这是上一个涉及完全相同主题的问题的后续内容Benefits of actor pattern in HTTP handler
下面我重复了这篇文章中的代码:
func (a *API) handleNext(w http.ResponseWriter, r *http.Request) {
var (
notFound = make(chan struct{})
otherError = make(chan error)
nextID = make(chan string)
)
a.action <- func() {
s, err := a.log.Oldest()
if err == ErrNoSegmentsAvailable {
close(notFound)
return
}
if err != nil {
otherError <- err
return
}
id := uuid.New()
a.pending[id] = pendingSegment{s, time.Now().Add(a.timeout), false}
nextID <- id
}
select {
case <-notFound:
http.NotFound(w, r)
case err := <-otherError:
http.Error(w, err.Error(), http.StatusInternalServerError)
case id := <-nextID:
fmt.Fprint(w, id)
}
}单个goroutine在幕后运行下面的循环,监听action通道。所有的突变都发生在这里,因为goroutine具有独占访问权,充当同步点:
func (a *API) loop() {
for {
select {
case f := <-a.action:
f()
}
}
}最初的帖子质疑这种模式的效用,因为它是handleNext块底部的select循环,直到发送给action chan的函数被触发(在专用的loop goroutine中),使得对handleNext的每个调用都串行运行。对原始问题的回答说明了“所有goroutines的总和”的总体好处,但我不确定我是否理解了这一点。
我现在的预期是,如果我们有10个连接的客户端,每个客户端都调用handleNext,它们都会立即被阻止,直到单个专用loop从action chan上拉出一个项目。由于只有一个专用的handleNext例程loop用于触发操作,并且这些操作必须完全完成,下一个goroutine才能继续执行,因此永远不会并发执行多个handleNext。
我知道这个模式避免了锁定的需要,因为所有的变化都会限制在loop goroutine上,但它不是也阻止了多个客户端同时工作吗?如果在loop内部对f()的调用改为go f(),那么将会并发执行handleNext函数,但这将违背该模式的目的,因为那时您将返回到需要在action函数内使用锁。
我一定是误解了这件事。
所以-我可以看到这种模式带来的好处是无锁同步,但这不是以一次只能在一个客户端上工作为代价的吗?如果这是真的,那么这与在没有锁或任何其他同步原语的情况下一次只处理一个请求有什么不同?
发布于 2021-09-16 12:18:16
注意,匿名函数可以启动goroutine并提前返回:
a.action <- func() {
gizmo, err := a.checkGizmoAvailable() // this is run synchronously
if err != nil {
otherErr <- err
return
}
// the remainder is run asynchronously
go func(){
s, err := gizmo.log.Oldest()
if err == ErrNoSegmentsAvailable {
close(notFound)
return
}
if err != nil {
otherError <- err
return
}
id := uuid.New()
// this will require synchronization :
a.pending[id] = pendingSegment{s, time.Now().Add(a.timeout), false}
nextID <- id
}()
// returning here, a.loop() can start the next function
}https://stackoverflow.com/questions/69202823
复制相似问题