提到Golang,都说Golang 天生高并发。所以分享一下我认为的Golang高并发精髓。
简单的并发执行util
package util
import (
"context"
"sync"
)
type parallelRunner struct {
ctx context.Context
cancel context.CancelFunc
channel chan func()
wg sync.WaitGroup
}
func NewParallelRunner(ctx context.Context, parallelism int) *parallelRunner {
ctx, cancel := context.WithCancel(ctx)
r := ¶llelRunner{
channel: make(chan func()),
ctx: ctx,
cancel: cancel,
}
for batchIdx := 0; batchIdx < parallelism; batchIdx++ {
r.wg.Add(1)
go func() {
for r.ctx.Err() == nil {
f, open := <-r.channel
if !open && f == nil {
break
}
f()
}
r.wg.Done()
}()
}
return r
}
func (r *parallelRunner) Run(handel func()) {
r.channel <- handel
}
func (r *parallelRunner) Wait() {
close(r.channel)
r.wg.Wait()
}
func (r *parallelRunner) Break() {
r.cancel()
}
这段代码先按照并发度起几个固定数量的goroution消费要执行的回调函数,然后提交回调函数,不需要等全部提交完毕就开始异步批处理了,提交结束后 Wait来等待全部执行结束,或者通过Break来中止执行。
另一个有趣的实现
type batchRunner struct {
in []func()
}
func NewBatchRun() *batchRunner {
return &batchRunner{
in: []func(){},
}
}
func (r *batchRunner) Submic(handel func()) {
r.in = append(r.in, handel)
}
func (r *batchRunner) Run(parallelism int) {
sem := make(chan struct{}, parallelism)
for _, f := range r.in {
sem <- struct{}{}
go func(fn func()) {
defer func() { <-sem }()
fn()
}(f)
}
for i := 0; i < parallelism; i++ {
sem <- struct{}{}
}
}
这个的用法和上面的会有不同:Submit提交后并不立即执行,而是在Run时才执行,不支持Break 和 Context超时取消。
之所以说这个实现精妙,是因为它没使用 WaitGroup, 也不需要提前起固定数量的协程。并发度、监听执行完成,都是通过 sem := make(chan struct{}, parallelism)
这么一个channel实现的。