使用channel控制并发数
不带超时的写法
package concurrent_test import ( "fmt" "math/rand" "sync" "testing" "time" ) func init() { rand.Seed(time.Now().UnixNano()) } // 使用channel控制并发数 var lst = []string{ "whw1", "whw2", "whw3", "whw4", "whw5", "whw6", "whw7", "whw8", "whw9", "whw10", "whw11", "whw12", "whw13", "whw14", "whw15", "whw16", "whw17", "whw18", "whw19", } func TestChanControlGos(t *testing.T) { var wg sync.WaitGroup limitChan := make(chan struct{}, 3) // 最多同时有3个协裎同时工作 for _, item := range lst { wg.Add(1) limitChan <- struct{}{} // 注意: 如果要用lst中的元素,一定要定义在func下并当作参数传入! go func(s string) { defer wg.Done() consumeStr(s, limitChan) }(item) } wg.Wait() fmt.Println("主协裎结束......") } func consumeStr(name string, limitCh chan struct{}) { defer func() { <-limitCh }() time.Sleep(time.Duration(rand.Intn(2)) * time.Second) // 模拟延迟 fmt.Println("name: ", name) }
带超时机制的写法1 -- 复杂一些
但是上面的代码有问题:如果缓冲区设置的很小,而某几个协裎处理时长又很长,会导致整个程序夯住,这个时候我们需要为每个协裎处理加一个超时的机制。
其实问题就变成了:查看子协裎是否超时,如果超时的话直接停止该子协裎(实际上也可以进行重试,这里就不展开了),避免这个超时的子协裎占用太多的资源:
package concurrent_test import ( "context" "fmt" "math/rand" "sync" "testing" "time" ) func init() { rand.Seed(time.Now().UnixNano()) } var lst3 = []string{ "whw1", "whw2", "whw3", "whw4", "whw5", "whw6", "whw7", "whw8", "whw9", "whw10", } func consumeName(ctx context.Context, wg *sync.WaitGroup, name string, limitCh chan struct{}, timeLimitCh chan bool) { // 没有超时,正常处理完成 defer func() { <-limitCh wg.Done() <-timeLimitCh timeLimitCh <- false // 如果没有超时,需要往channel中放入false,这样外面就不会重复“释放了” fmt.Printf("{%s} 处理完成并释放资源! \n", name) }() // 模拟程序的延迟 randInt := rand.Intn(5) fmt.Printf("{%s} 的处理时间是: %d \n", name, randInt) time.Sleep(time.Second * time.Duration(randInt)) } func TestChanCtlGos(t *testing.T) { var wg sync.WaitGroup // NOTE 协裎并发限制数 limitChan := make(chan struct{}, 2) for _, item := range lst3 { wg.Add(1) // 开协裎前往带缓冲的channel中放数据 // NOTE 如果channel满了会夯住,直到子协裎从channel中踢出数据 limitChan <- struct{}{} go func(name string) { // 判断下面的 consumeName 有没有“超时” —— 如果超时了释放limitChan与wg.Done()需要写在select里面,如果没有超时就把他们写在函数里面 // 注意:释放limitChan与wg.Done() 只能在consumeName函数 “超时”与“不超时”时使用一次! // 每个协裎里面再加一个标识函数是否超时的channel: true表示超时,false表示不超时 ———— Go使用channel进行协裎之间的通信! timeLimitedChan := make(chan bool, 1) // 需要设置缓冲区,不设置的话程序会夯住 timeLimitedChan <- true // 默认超时 // 在每个协裎中开一个1秒超时的ctx,也就是说每个协裎有1秒的处理超时时间 ctxTimeOut, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() go consumeName(ctxTimeOut, &wg, name, limitChan, timeLimitedChan) select { // NOTE 实际中根据自己的业务情况来选择Done的时机 // 到达ctx限制的超时时间后,需要配合Done方法,此时该协裎会终止!我们需要做一些业务的“释放处理” // 实际上一定会到ctx.Done的,此时不能盲目释放资源,应该在 consumeName 函数真正超时的时候再去释放 case <-ctxTimeOut.Done(): // consumeName 函数真正超时的标志 bl := <-timeLimitedChan if bl { fmt.Printf("%s 超时了! \n", name) <-limitChan // 子协裎被终止了,此时应该在超时后手动从limitChan中释放数据 wg.Done() // 子协裎终止了,手动wg.Done(),否则程序会夯住 } } }(item) } wg.Wait() fmt.Println("程序结束......") }
带超时机制的写法2 —— 使用taskDoneChan&加一条select的case语句进行优化
package concurrent_test import ( "context" "fmt" "math/rand" "sync" "testing" "time" ) func init() { rand.Seed(time.Now().UnixNano()) } var lst4 = []string{ "whw1", "whw2", "whw3", "whw4", "whw5", "whw6", "whw7", "whw8", "whw9", "whw10", } func consumeName2(ctx context.Context, wg *sync.WaitGroup, name string, limitCh chan struct{}, taskDoneChan chan bool) { // 没有超时,正常处理完成 defer func() { // 往taskDoneChan中放入任务已经完成的标志 taskDoneChan <- true fmt.Printf("{%s} 处理完成并释放资源! \n", name) }() // 模拟程序的延迟 randInt := rand.Intn(5) fmt.Printf("{%s} 的处理时间是: %d \n", name, randInt) time.Sleep(time.Second * time.Duration(randInt)) } func TestChanCtlGos2(t *testing.T) { var wg sync.WaitGroup // NOTE 协裎并发限制数 limitChan := make(chan struct{}, 2) for _, item := range lst4 { wg.Add(1) // 开协裎前往带缓冲的channel中放数据 // NOTE 如果channel满了会夯住,直到子协裎从channel中踢出数据 limitChan <- struct{}{} go func(name string) { // 标识任务是否完成的channel taskDoneChan := make(chan bool) // 在每个协裎中开一个1秒超时的ctx,也就是说每个协裎有1秒的处理超时时间 ctxTimeOut, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() go consumeName2(ctxTimeOut, &wg, name, limitChan, taskDoneChan) select { // 如果consume超时了就会走这里,需要释放信号量 case <-ctxTimeOut.Done(): fmt.Printf("%s 超时了! \n", name) <-limitChan wg.Done() // consume没有超时的情况走这里,同样需要释放信号量 case taskDone := <-taskDoneChan: if taskDone { <-limitChan wg.Done() fmt.Printf("%s 正常完成! \n", name) } } }(item) } wg.Wait() fmt.Println("程序结束......") }