簡介
go語言的協程是十分輕量級的線程,它的銷毀創建都在用戶空間,因此一般沒有必要對協程進行限制,但是某些場景還是需要控制并發數的。
1、可重復利用的協程
//協程池定義
type Pool struct {
work chan func() //工作協程的chan,無緩沖區(同步)
sem chan struct{} //控制并發數,帶緩沖區
}
Step1:初始化協程池
func NewPool(size int) *Pool{
return &Pool{
work: make(chan func()),
sem: make(chan struct{}, size),
}
}
Step2:任務Push接口
func (pool *Pool)NewTask(task func()) {
select {
case pool.work <-task:
fmt.Println("pool.work sends success.")
case pool.sem <- struct{}{}:
go pool.worker(task)
}
}
1、work chan是一個無緩沖的chan,此處作為生產者往chan內push,如果消費者不存在,不會push成功,也就是select事件不會觸發,就會走第二個case。
2、如果兩個case都存在事件,那么select會挑選一個分支執行,也即是說,開啟的協程是根據當前是否有空閑協程可用,如果有則可能復用,如果沒有,則新開協程執行新的任務,協程上限為設置的最大值。
Step3:接收任務,執行任務
func (pool *Pool)worker(task func()) {
defer func() {
<- pool.sem //理論上是不會走這個流程
}()
//重復利用開啟的goroutine
for {
task()
//消費者 (如果消費者沒準備好,同步的channel就不會發送成功,也就是pool.work <-task 事件不會被觸發
task = <-pool.work
}
}
優點:可最大化利用協程資源,如果任務頻繁,可使用該方式
缺點:
1、開啟的協程資源不會被回收,即使沒有任務。
2、如果任務中包含阻塞操作,會引發其他任務無法獲取資源而一直處于等待
2、每次新建協程執行
與第一種方法一樣,使用chan來控制并發數,但是不需要work chan,因為每次都是新建協程,在未達到最大并發之前,直接執行即可。
type Pool2 struct {
sem chan struct{}
}
func NewPool2(size int) *Pool2{
return &Pool2{
sem: make(chan struct{}, size),
}
}
func (pool *Pool2)NewTask(task func()) {
select {
case pool.sem <- struct{}{}:
go pool.Worker(task)
}
}
func (pool *Pool2)Worker(task func()) {
defer func() {
fmt.Println("go routing ends.")
<-pool.sem
}()
//此處不能使用go 開啟協程,如果開啟go,那么defer的內容會被直接執行,也就是達不到限制同時使用的goroutine 數量
task()
}