Golang 演员模式
Posted
技术标签:
【中文标题】Golang 演员模式【英文标题】:Golang Actor Pattern 【发布时间】:2021-11-11 02:46:45 【问题描述】:这是上一个问题的后续问题,涵盖完全相同的主题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] = pendingSegments, 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 在后台运行下面的循环来监听动作通道。所有的突变都发生在这里,因为 goroutine 具有独占访问权限,充当同步点:
func (a *API) loop()
for
select
case f := <-a.action:
f()
原帖质疑这种模式的实用性,因为handleNext
底部的选择循环阻塞,直到发送到action
chan 的函数被触发(在专用的loop
goroutine 中),每次调用handleNext
串行运行。对原始问题的回答说明了“所有 goroutine 的总和”的总体好处,但我不确定我是否理解这种情况。
我目前的期望是,如果我们有 10 个客户端连接,每个客户端都调用 handleNext
,它们都会立即被阻止,直到单个专用 loop
从 action
chan 中提取一个项目。由于只有一个专用的 goroutine loop
用于触发动作,并且这些动作必须在下一个 handleNext
goroutine 继续之前完全完成,因此永远不会有超过一个 handleNext
的并发执行。
我知道这种模式避免了锁定的需要,因为所有突变都将被限制在 loop
goroutine 中,但它不是也阻止了多个客户端同时工作吗?如果在loop
内部,对f()
的调用是go f()
,那么handleNext
函数将同时执行,但这会破坏模式的目的,因为那时你会回到需要使用锁在action
函数内。
我一定是误会了这里的东西。
所以 - 我可以看到我们有无锁同步作为这种模式的一个好处,但这不是以一次只处理一个客户端为代价的吗?如果这是真的,那么这与一次只处理一个请求、串行处理、没有锁或任何其他同步原语有什么不同?
【问题讨论】:
“这不是以一次只在一个客户端上工作为代价的吗”:在他的演讲中,他通过重写代码来说明这一部分,该代码一次也只运行一个动作。我猜他并不是在建议您按照这种正确的方式安装所有代码,他更多地提到他发现拥有func()
s 的单个频道而不是一堆频道fooReqC, barReqC, ...
how would this be different than just handling one request at a time, serially, without locks or any other synchronization primitives?
如果不使用通道或互斥锁,如何在同时调用的函数中实现它?
@LeGEC 是的,我知道 - 早期的模式是相同的想法,只是每个动作都有一个通道,而不是一个处理所有动作的闭包通道。我想这个问题适用于这两种模式。我意识到这不是“一种真正的方式”,但如果它没有同时运行 api 函数,有什么好处?
@mh-cbon - 你一次只有一个 goroutine 处理 API 请求。
【参考方案1】:
请注意,匿名函数可以启动一个 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] = pendingSegments, time.Now().Add(a.timeout), false
nextID <- id
()
// returning here, a.loop() can start the next function
【讨论】:
好的,这基本上就是我在上面第 3 段中提到的场景(在问题的底部),对吧?如果没有在单独的 goroutine(或调用一个)中运行操作,整个事情将是同步的,一次不会处理多个请求。这里我们引入了异步来允许在多个客户端上工作,但是现在我们必须再次使用锁——对吗?我不确定演讲中提出的动作函数是否假定在单独的 goroutine(或调用一个)中运行,因为它没有在 sn-p 中显示 - 我可能应该查找完整的源代码。 yes : 如果你的代码允许并发访问某些东西,你需要选择一种同步方式。 对 - 所以原来的问题仍然存在。如果这种模式的主要好处是无锁同步,那么在不同的 goroutine 中运行checkGizmoAvailable()
会破坏意图,因为我们回到了锁。如果我们不在一个单独的 goroutine 中运行它,那这不就等同于只在一个 goroutine 中运行所有东西而没有所有的通道杂技吗?以上是关于Golang 演员模式的主要内容,如果未能解决你的问题,请参考以下文章