以可视化方式解释 Go 并发 – 通道

在并发编程中,许多编程语言采用共享内存/状态模型。然而,Go 通过实现 通信顺序进程 (CSP) 区别于众多语言。在 CSP 中,一个程序由并行的进程组成,这些进程不共享状态,而是使用通道进行通信和同步它们的操作。因此,对于有意采用 Go 的开发人员来说,理解通道的工作原理变得至关重要。在本文中,我将使用 Gopher 运行他们的虚构咖啡馆的可爱比喻来阐述通道,因为我坚信人类更容易通过视觉学习。

情景

Partier、Candier 和 Stringer 经营一家咖啡馆。由于制作咖啡需要比接受订单更多的时间,Partier 将协助接受客户的订单,然后将这些订单传递到厨房,Candier 和 Stringer 在那里制作咖啡。

 

Gopher\\’s Cafe

无缓冲通道

最初,咖啡馆以最简单的方式运营:每当收到新订单时,Partier 将订单放入通道中,并等待 Candier 或 Stringer 中的任何一个在接受新订单之前取走它。这种 Partier 和厨房之间的通信是通过无缓冲通道实现的,使用 ch := make(chan Order) 创建。当通道中没有待处理的订单时,即使 Stringer 和 Candier 都准备好接受新订单,它们也会保持空闲状态,等待新订单到来。

以可视化方式解释 Go 并发 – 通道
 

无缓冲通道

当收到新订单时,Partier 将其放入通道中,使订单可以被 Candier 或 Stringer 之一接受。但是,在继续接受新订单之前,Partier 必须等待其中一个从通道中获取订单。

以可视化方式解释 Go 并发 – 通道
 

由于 Stringer 和 Candier 都可以接受新订单,因此订单将立即被其中一个接受。但是,不能保证或预测哪个具体的接收者会获取订单。在 Stringer 和 Candier 之间的选择是非确定性的,它依赖于诸如调度和 Go 运行时的内部机制等因素。假设 Candier 获取了第一个订单。

以可视化方式解释 Go 并发 – 通道
 

Candier 完成处理第一个订单后,她回到等待状态。如果没有新订单到达,那么 Candier 和 Stringer 这两名工作人员都会保持空闲状态,直到 Partier 将另一个订单放入通道中供他们处理。

以可视化方式解释 Go 并发 – 通道
 

当新订单到达并且 Stringer 和 Candier 都可以处理它时,即使 Candier 刚刚处理了前一个订单,接收新订单的具体工作人员仍然是不确定的。在这种情况下,假设 Candier 再次被分配为第二个订单的接收者。

以可视化方式解释 Go 并发 – 通道
 

在新订单 order3 到达时,Candier 正在处理 order2,她没有等待在行 order := <-ch 处,因此 Stringer 成为唯一可以接收 order3 的工作人员。因此,他会接收到它。

以可视化方式解释 Go 并发 – 通道
 

在将 order3 发送给 Stringer 后不久,order4 到达。此时,Stringer 和 Candier 已经忙于处理各自的订单,没有人可以接收 order4。由于通道没有缓冲,将 order4 放入通道会阻塞 Partier,直到 Stringer 或 Candier 可以接收 order4 为止。这种情况值得特别注意,因为我经常看到人们对无缓冲通道(使用 make(chan order) 或 make(chan order, 0) 创建)和具有缓冲大小为 1 的通道(使用 make(chan order, 1) 创建)感到困惑。因此,他们错误地期望 ch <- order4 立即完成,允许 Partier 在 ch <- order5 处被阻塞之前接受 order5。如果您也是这样认为的,我已经在 Go Playground 上创建了一个代码片段,以帮助您纠正您的误解 https://go.dev/play/p/shRNiDDJYB4。

 

带缓冲通道

无缓冲通道是有效的,但它限制了总吞吐量。如果他们只接

受一些订单以便在后端(厨房)顺序处理它们,那将更好。这可以通过使用带缓冲通道来实现。现在,即使 Stringer 和 Candier 忙于处理他们的订单,只要通道不满,例如最多 3 个待处理订单,Partier 仍然可以将新订单放入通道并继续接受其他订单。

1*TCrrdUaa7XPcmRSDIO7xEQ.png

引入带缓冲通道后,咖啡馆增强了处理更多订单的能力。然而,选择适当的缓冲区大小以保持客户合理的等待时间非常重要。毕竟,没有客户想忍受过长的等待时间。有时,拒绝新订单可能比接受新订单但无法及时完成它们更可接受。此外,在使用带缓冲通道的瞬时容器化(Docker)应用程序时要小心,因为预计会随机重启,在这种情况下,从通道中恢复消息可能是一项具有挑战性甚至不可能的任务。

通道 vs 阻塞队列

尽管基本上不同,Java 中的阻塞队列用于线程之间的通信,而 Go 中的通道用于 Goroutine 的通信,但阻塞队列和通道在某种程度上表现出相似之处。如果您熟悉阻塞队列,那么理解通道肯定会更容易。

常见用途

通道是 Go 应用程序中的基本和广泛使用的功能,可以用于各种用途。通道的一些常见用例包括:

?Goroutine 通信:通道允许不同 Goroutine 之间进行消息交换,使它们能够协作而无需直接共享状态。?工作池:如上面的示例所示,通道经常用于管理工作池,其中多个相同的工作程序从共享通道中处理传入任务。?分发和汇总:通道促进了分发和汇总模式,多个 Goroutine(分发)执行工作并将结果发送到单个通道,而另一个 Goroutine(汇总)消耗这些结果。?超时和截止期:通道与 select 语句结合使用,可以用于处理超时和截止期,确保程序可以优雅地处理延迟并避免无限等待。

我将在其他文章中更详细地探讨通道的不同用法。但是,目前,让我们通过实现上述咖啡馆场景来结束这篇介绍性博客,并观察通道如何在其中发挥作用。我们将探讨 Partier、Candier 和 Stringer 之间的互动,并观察通道如何促进它们之间的顺畅通信和协调,从而实现咖啡馆中的高效订单处理和同步。

演示代码

package main
import ( \\\"fmt\\\" \\\"log\\\" \\\"math/rand\\\" \\\"sync\\\" \\\"time\\\")
func main() { ch := make(chan order, 3) wg := &sync.WaitGroup{} wg.Add(2)
go func() { defer wg.Done() worker(\\\"Candier\\\", ch) }()
go func() { defer wg.Done() worker(\\\"Stringer\\\", ch) }()
for i := 0; i < 10; i++ { waitForOrders() o := order(i) log.Printf(\\\"Partier: I %v, I will pass it to the channel\\\\n\\\", o) ch <- o }
log.Println(\\\"No more orders, closing the channel to signify workers to stop\\\") close(ch)
log.Println(\\\"Wait for workers to gracefully stop\\\") wg.Wait()
log.Println(\\\"All done\\\")}
func waitForOrders() { processingTime := time.Duration(rand.Intn(2)) * time.Second time.Sleep(processingTime)}
func worker(name string, ch <-chan order) { for o := range ch { log.Printf(\\\"%s: I got %v, I will process it\\\\n\\\", name, o) processOrder(o) log.Printf(\\\"%s: I completed %v, I\\\'m ready to take a new order\\\\n\\\", name, o) }
log.Printf(\\\"%s: I\\\'m done\\\\n\\\", name)}
func processOrder(_ order) { processingTime := time.Duration(2+rand.Intn(2)) * time.Second time.Sleep(processingTime)}
type order int
func (o order) String() string { return fmt.Sprintf(\\\"order-%02d\\\", o)}

您可以复制此代码,在您的 IDE 上进行调整并运行,以更好地理解通道的工作原理。

相关系列文章:

使用通信顺序进程(CSP)模型的 Go 语言通道

Go并发可视化解释 – Select语句

原创文章,作者:小技术君,如若转载,请注明出处:https://www.sudun.com/ask/33965.html

(0)
小技术君's avatar小技术君
上一篇 2024年4月20日 下午5:14
下一篇 2024年4月20日 下午5:16

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注