如何优雅的同步化异步代码,一直以来都是各大编程语言致力于优化的点,记得最先是C# 5.0加入了async/await来简化TPL的多线程模型,后来Javascript的Promise也吸收这一语法糖,在ES 6中也加入了async和await.node
那么,被你们一称赞并发性能好、异步模型独树一帜的golang,可否也有async和await呢?git
其实,这对于golang的CSM来讲是一点也不难!github
核心代码以下:golang
done := make(chan struct{})
go func() {
// do work asynchronously here
//
close(done)
}()
<-done
复制代码
是否是很简单呢? go rountine负责async, channel的负责await, 简直是完美!编程
但这个代码看起来仍是有点丑,并且这个go func(){}
尚未返回值,虽然说能够经过闭包来接收返回值,但那个代码就更难维护了。设计模式
代码难看没关系,只要Don't repeat yourself (DRY),封装一下不就行了?promise
type WorkFunc func() (interface{}, error) func NewPromise(workFunc WorkFunc) *Promise {
promise := Promise{done: make(chan struct{})}
go func() {
defer close(promise.done)
promise.res, promise.err = workFunc()
}()
return &promise
}
func (p *Promise) Done() (interface{}, error) {
<-p.done
return p.res, p.err
}
复制代码
调用的代码以下:多线程
promise := NewPromise(func() (interface{}, error) {
// do work asynchronously here
//
return res, err
})
// await
res, err := promise.Done()
复制代码
是否是美观了许多呢?闭包
这个实现和Javascript的Promise的API是有很大差距,使用体验上由于golang没有泛型,也须要转来转去的,但为了避免辜负Promise这个名字,怎么能没有then
呢?并发
type SuccessHandler func(interface{}) (interface{}, error) type ErrorHandler func(error) interface{}
func (p *Promise) Then(successHandler SuccessHandler, errorHandler ErrorHandler) *Promise {
newPromise := &Promise{done: make(chan struct{})}
go func() {
res, err := p.Done()
defer close(newPromise.done)
if err != nil {
if errorHandler != nil {
newPromise.res = errorHandler(err)
} else {
newPromise.err = err
}
} else {
if successHandler != nil {
newPromise.res, newPromise.err = successHandler(res)
} else {
newPromise.res = res
}
}
}()
return newPromise
}
复制代码
有了then
能够chain起来,是否是找到些Promise的感受呢?
完整代码请查看 promise.go
原本个人理解也就到些了,而后前段时间(说来也是一月有余了),看了Go并发设计模式之 Active Object这篇文章后, 发现若是有一个常驻协程在异步的处理任务,并且是FIFO的,那么这实际上是至关于一个无锁的设计,能够简化对临界资源的操做。
因而,我照着文章的思路,实现了下面的代码:
// Creates a new actor
func NewActor(setActorOptionFuncs ...SetActorOptionFunc) *Actor {
actor := &Actor{buffer: runtime.NumCPU(), quit: make(chan struct{}), wg: &sync.WaitGroup{}}
for _, setOptionFunc := range setActorOptionFuncs {
setOptionFunc(actor)
}
actor.queue = make(chan request, actor.buffer)
actor.wg.Add(1)
go actor.schedule()
return actor
}
// The long live go routine to run.
func (actor *Actor) schedule() {
loop:
for {
select {
case request := <-actor.queue:
request.promise.res, request.promise.err = request.work()
close(request.promise.done)
case <-actor.quit:
break loop
}
}
actor.wg.Done()
}
// Do a work.
func (actor *Actor) Do(workFunc WorkFunc) *Promise {
methodRequest := request{work: workFunc, promise: &Promise{
done: make(chan struct{}),
}}
actor.queue <- methodRequest
return methodRequest.promise
}
// Close actor
func (actor *Actor) Close() {
close(actor.quit)
actor.wg.Wait()
}
复制代码
一个简单的没啥意义的纯粹为了demo的测试用例以下:
func TestActorAsQueue(t *testing.T) {
actor := NewActor()
defer actor.Close()
i := 0
workFunc := func() (interface{}, error) {
time.Sleep(1 * time.Second)
i++
return i, nil
}
promise := actor.Do(workFunc)
promise2 := actor.Do(workFunc)
res2, _ := promise2.Done()
res1, _ := promise.Done()
if res1 != 1 {
t.Fail()
}
if res2 != 2 {
t.Fail()
}
}
复制代码
完整代码请查看 actor.go
每一个语言都有它的独特之处,在个人理解中,玩转golang的CSM模型,channel必定要用的6。
因而,我建立了Channelx这个repo, 包含了对channel经常使用场景的封装,欢迎你们审阅,喜欢的就点个star。
此系列其它文章:
如何把Golang的channel用的如nodejs的stream同样丝滑