golang 中的并发控制与优雅结束
提到 golang 并发,就可以联想到下列关键词:
- goroutine
- channel
- sync.WaitGroup
- sync.Mutex
- tunny/ants协程池
- for-select
在一般的场景中,比如碰到这类场景,需要控制并发协程的数量,通常的话,我们可以通过 channel 或者 类似协程池功能,或者就是直接用现成的三方库-协程池,总之这些都是常用的一些方案,所以由此我们大概总结以下几种并发控制的方式。
并发控制
通过 channel 实现信号通知
先看代码:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
// sync.WaitGroup + channel
func main() {
// concurrency count
var concurrency int = 3
// define sem
sem := make(chan struct{}, concurrency)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
worker(id, sem)
}(i)
}
wg.Wait()
fmt.Println("Main goroutine end.")
}
func worker(id int, sem chan struct{}) {
// 占用一个信号量
sem <- struct{}{}
// 运行结束后,释放信号量
defer func() {
<- sem
}()
// exec task
rand.Seed(time.Now().UnixNano())
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
fmt.Printf("Time: %v, Worker %d: working...\n", time.Now().Format(time.RFC3339Nano), id)
}
通过定义一个给定缓冲的 channel 当做一个信号池,根据 channel 缓冲特性及阻塞特性,实现并发数量的限制或者控制。
自定义简单的协程池
先看代码:
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int) {
//jobs等待主要协程往jobs放数据
for j := range jobs {
fmt.Printf("协程池 %d: 协程池正在工作 %d\n", id, j)
results <- j
}
}
func main() {
const numJobs = 5 //协程要做的工作数量
const numWorkers = 3 //协程池数量
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// 启动协程池
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
worker(id, jobs, results)
}(i)
}
// 提交任务
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// 等待所有工作完成
go func() {
wg.Wait()
close(results)
}()
// 处理结果
for result := range results {
fmt.Println("Result:", result)
}
}
通过现有的 tunny/ants 协程池
package main
import (
"log"
"time"
"github.com/Jeffail/tunny"
)
func main() {
pool := tunny.NewFunc(10, func(i interface{}) interface{} {
log.Println(i)
time.Sleep(time.Second)
return nil
})
defer pool.Close()
for i := 0; i < 500; i++ {
go pool.Process(i)
}
time.Sleep(time.Second * 4)
}
并发协程的优雅结束
for-range
先看代码:
package main
import (
"fmt"
"time"
)
// for-range channel
func main() {
jobs := make(chan int, 10)
stop := make(chan struct{})
defer close(stop)
// publish jobs
go func() {
for i := 0; i < 20; i++ {
jobs <- i
}
close(jobs)
}()
// exec jobs
go func(job <- chan int) {
for num := range job {
fmt.Printf("Time: %v, exec job: %d\n", time.Now().Format(time.RFC3339Nano), num)
}
stop <- struct{}{}
}(jobs)
// 等待发送信号结束 主goroutine
<- stop
fmt.Println("Main goroutine end.")
}
主要利用 channel 关闭后,通过 for-range 操作自动退出特性实现子协程优雅退出。
for-select
参考: