golang goroutine实现_G7.1 Go语言中使用通道chan实现goroutine池

本文介绍了如何在Go语言中利用通道实现goroutine池,通过令牌池控制并发处理请求的数量。当收到网络请求时,程序会从线程池中取出线程进行处理,处理完成后线程归还到池中。代码示例展示了如何创建请求结构体,定义令牌池和请求队列通道,以及处理请求的函数。在main函数中,模拟发送20个请求,大部分请求成功处理,部分因令牌池为空未被处理。此方法有效地限制了并发请求,避免资源过度消耗。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一般的计算机语言在处理并发任务时,经常会按需创建线程,例如在Web服务器应用中,接收到一个网络请求(network request)就会新建一个线程来处理。但系统资源有限,一般会控制所建线程个数的上限,而控制上限的一种方法就是使用“线程池”:例如我们把可用的n个线程抽象成一个线程池,该池中就有n个线程,需要处理请求时会从其中取出一个线程来使用,使用完毕后该线程将被放回池中,如果池中的线程都已经被取用,则新来的请求将被阻塞等待直至有可用的线程为止,另一种处理的方法是直接向该请求者报告异常,请求将被中止。在Go语言中线程对应的是goroutine,本节将演示如何用通道来实现goroutine池,由于没有必要直接将goroutine对应的函数直接放入通道中,因此也可用类似令牌的方式来控制goroutine的最大数量,形成虚拟的goroutine池,也可以看作将goroutine池转换成了令牌池。

package main

import (

"math/rand"

"time"

t "tools"

)

// Request1 是自定义的结构类型

// 用于发送请求时传递所需的所有数据

type Request1 struct {

ID      int           //本次请求的编码,是随机产生的整数

Count   int           //计算多少次

Response chan []string //用于存放请求结果的通道

}

// 令牌池

var poolG chan int

// 缓存请求的通道

var requestChannelG chan Request1

// 发送请求并等待请求执行结果的函数

func sendRequest(countA int) {

idT := rand.Intn(10000)

responseChanT := make(chan []string, 1)

defer close(responseChanT)

requestChannelG

responseT :=

t.Printfln("goroutine(ID: %v, Count: %v)结果: %#v", idT, countA, responseT)

}

// 处理具体每个请求的函数

func doRequest(requestA Request1) {

resultT := 0

for i := 0; i < requestA.Count; i++ {

resultT = i + requestA.Count

}

requestA.Response

poolG

}

// 处理请求队列(通道)的函数

// 根据令牌池是否有剩余令牌来决定是否启动处理具体请求的goroutine

func processRequests() {

for {

requestT :=

if len(poolG) > 0 {

go doRequest(requestT)

} else {

requestT.Response

}

}

}

func main() {

//为令牌池分配容量,容量虽然为10

//但可用令牌是根据后面放入的令牌个数来确定的

poolG = make(chan int, 10)

//为请求缓冲通道分配100个的容量

//如果缓冲通道内超过100个请求,下一个请求将被阻塞

requestChannelG = make(chan Request1, 100)

//确保通道退出时会被关闭

defer close(requestChannelG)

defer close(poolG)

//启动请求处理主goroutine

go processRequests()

//在令牌池中放入5块令牌

for i := 0; i < 5; i++ {

poolG

}

//模拟发送20个请求

for i := 0; i < 20; i++ {

go sendRequest(1000)

}

//主线程死循环以保证处理请求的goroutine一直执行

//需要用Ctrl+C键来退出程序运行

for {

time.Sleep(100 * time.Millisecond)

}

}

代码 11‑9 goroutine2/goroutine2.go

代码11‑9中,使用通道poolG作为令牌池,来控制处理请求队列的主任务processRequests能够生成子任务(也就是由处理具体每个请求的doRequest函数生成的goroutine)的数量。代码中的要点说明如下:

-> poolG通道代表令牌池,里面有几块令牌就表明可以启动多少个处理请求的goroutine,也即是令牌池的可用令牌数限制了同时可以并发处理的请求数;注意poolG的容量并不代表可用令牌数,而是该通道中的数值项数表示令牌数;

-> requestChannelG通道用于缓存收到的所有请求,该通道的数值项类型是我们自定义的Request1,发送的请求必须符合该类型,Request1类型中的Response字段又是一个通道类型,用于接收该请求的响应信息;

-> sendRequest函数用于模拟创建单个的请求,它将产生一个随机的请求标识号填入Request1.ID字段中,然后指定要求计算的次数放入Request1.Count,还会创建用于接收响应信息的通道放入Request1.Response中,之后该函数就会通过该通道等待接收返回的响应信息后输出,最后退出该goroutine的运行;

-> processRequests函数用于处理请求队列和分派goroutine来处理,它会循环读取requestChannelG通道中的请求,一旦接收到一个请求时,它会首先判断是否还有可用的令牌,这是通过len(poolG)表达式来获取poolG通道的当前长度来判断的,如果不为0,则表明通道里面有数值项,也就代表还有可用令牌,此时通过

-> doRequest函数用于处理单个请求,它在完成了指定的计算任务之后,会在该请求的响应通道中写入请求成功的信息和计算结果,然后在退出之前交还令牌(通过向poolG通道中写入一个数据来实现);

-> 主函数main中,首先定义了令牌池poolG通道和请求队列requestChannelG通道,并为它们分配了容量;然后启动processRequests函数的goroutine来循环处理请求;之后往令牌池poolG中放入了5块令牌,代表着本程序将能够同时并发处理5个请求;然后模拟发送了20个请求来测试其请求处理能力;最后用一个无限循环(也叫死循环)来保证程序一直运行,并通过time.Sleep函数来定时休眠一下,这样可以保证程序不占用太多的系统资源,并且在休眠时其他goroutine能得到更多的调度执行时间。注意,由于main函数最后的死循环,本程序必须用Ctrl-C快捷键才能终止运行。

该代码执行结果如下:

goroutine(ID: 3300, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 8081, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 1211, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 456, Count: 1000)结果: []string{"请求失败", "goroutine池已空"}

goroutine(ID: 2540, Count: 1000)结果: []string{"请求失败", "goroutine池已空"}

goroutine(ID: 7887, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 1847, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 2081, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 4425, Count: 1000)结果: []string{"请求失败", "goroutine池已空"}

goroutine(ID: 1318, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 694, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 8511, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 5089, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 8162, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 4728, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 3274, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 9106, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 1445, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 3237, Count: 1000)结果: []string{"请求成功", "1999"}

goroutine(ID: 4059, Count: 1000)结果: []string{"请求成功", "1999"}

可以看出,20个模拟发送的请求大部分被处理了,但有几个请求由于令牌池当时为空,所以没有被处理。

本例主要说明的要点包括:

-> 利用通道可以实现令牌池或虚拟的goroutine池等,用于控制goroutine等资源的数量上限,避免系统资源被无限制地使用;

-> 通道的数值项类型也可以是复合类型或者自定义的结构类型,这使得通道的可用性大大增强,例如,可以使用结构类型的通道在做goroutine归并时分别判断出不同类型的goroutine执行完毕的个数等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值