введение
Как элегантно синхронизировать асинхронный код, всегда было в центре внимания основных языков программирования для оптимизации. Я помню, что в C# 5.0 добавили async/await для упрощения многопоточной модели TPL. Позднее этот синтаксический сахар поглотил и Javascript's Promise. async и await также были добавлены в ES 6.
Итак, может ли golang, который все хвалят за хорошую производительность параллелизма и уникальную асинхронную модель, также иметь async и await?
На самом деле, для CSM golang это совсем не сложно!
Основной код выглядит следующим образом:
done := make(chan struct{})
go func() {
// do work asynchronously here
//
close(done)
}()
<-done
Это очень просто? Go рутина отвечает за асинхронность, а канал отвечает за ожидание, что идеально!
Но код все еще выглядит немного некрасиво, и этоgo func(){}
Возвращаемого значения пока нет, и хотя можно получить возвращаемое значение через замыкание, этот код сложнее поддерживать.
Go Promise
Не имеет значения, уродлив ли код, пока не повторяйтесь (DRY), почему бы не инкапсулировать его?
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 API Promise.С точки зрения опыта использования, поскольку в 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
Это может быть цепочкой, вы находите какое-то чувство Обещания?
Пожалуйста, смотрите полный кодpromise.go
Actor
Изначально до меня дошло понимание, а потом какое-то время назад(Это чуть больше месяца.), видетьАктивный объект шаблона проектирования Go ConcurrencyПосле этой статьи я обнаружил, что если есть резидентная сопрограмма, обрабатывающая задачи асинхронно и по принципу FIFO, то это фактически эквивалентно lock-free дизайну, который может упростить работу с критичными ресурсами.
Итак, по идее статьи я реализовал следующий код:
// 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()
}
Простой и бессмысленный тестовый пример чисто для демонстрации выглядит следующим образом:
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
Суммировать
Каждый язык имеет свою уникальность, в моем понимании, чтобы играть с моделью CSM golang, канал должен быть 6.
Итак, я создалChannelxЭтот репозиторий содержит инкапсуляцию общих сцен канала. Вы можете просмотреть его. Если вам это нравится, нажмите звездочку.
Другие статьи из этой серии:
Как реализовать пакетную обработку сообщений с помощью канала Golang
Как использовать канал Golang так же плавно, как поток nodejs