当前位置: 首页 > news >正文

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

在并发编程中,许多编程语言采用共享内存/状态模型。然而,Go 通过实现 通信顺序进程(CSP)模型来区别于众多。在CSP中,程序由不共享状态的并行进程组成;相反,它们通过通道进行通信和同步操作。因此,对于有兴趣采用Go的开发人员来说,理解通道的工作原理变得至关重要。在本文中,我将使用愉快的比喻,描述Gophers运营他们的想象中的咖啡馆,因为我坚信人类更适合通过视觉学习。

场景

Partier、Candier 和 Stringer 正在经营一家咖啡馆。鉴于制作咖啡比接受订单需要更多时间,Partier 将负责从顾客那里接受订单,然后将这些订单传递给厨房,Candier 和 Stringer 将准备咖啡。

011465bd7bd2d53ab38812c98ace599b.png

无缓冲通道

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

7221ab876b327e9d858f1387f3200fd2.png

无缓冲通道

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

715040732a52945761ff310d468b4ed1.png

由于 Stringer 和 Candier 都可以接受新订单,所以新订单将立即被其中一个接受。然而,具体的接收者无法保证或预测。Stringer 和 Candier 之间的选择是不确定的,它取决于诸如调度和Go运行时的内部机制等因素。假设 Candier 得到了第一个订单。

a9f9fa268d0cc10377aec091013be776.png

在 Candier 完成处理第一个订单后,她返回等待状态。如果没有新订单到达,Candier 和 Stringer 两个工作者都将保持空闲状态,直到 Partier 再次将订单放入通道中以供处理。

d82446bb2d56589d2bd2b8ca402609bf.png

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

ed49c996a3e42661c036caa009451114.png

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

1fda92f75f4155599e1d68536a3a8dd2.png

在 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 在被阻塞之前接受 order5。如果您也是这样认为的,我已经在Go Playground上创建了一个代码片段,以帮助您纠正这种误解:https://go.dev/play/p/shRNiDDJYB4。

44cd09c341c94d75a1a14a96b572c7a2.png

带缓冲通道

无缓冲通道可以工作,但它限制了整体吞吐量。如果他们只接受一些订单来按顺序在后台(厨房)处理它们,那会更好。这可以通过带缓冲通道实现。现在,即使 Stringer 和 Candier 忙于处理他们的订单,只要通道不满,即可让 Partier 将新订单放入通道中,并继续接受额外的订单,例如最多3个未处理订单。

742e9ec62cf1d12d32ce2aa78c0e5684.png

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

通道 vs 阻塞队列

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

常见用途

通道是Go应用程序的一个基本且广泛使用的特性,具有多种用途。通道的一些常见用途包括:

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

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

代码示例

package mainimport ("fmt""log""math/rand""sync""time"
)func main() {ch := make(chan order, 3)wg := &sync.WaitGroup{} // 更多关于 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.Secondtime.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.Secondtime.Sleep(processingTime)
}type order intfunc (o order) String() string {return fmt.Sprintf("order-%02d", o)
}

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

http://www.lryc.cn/news/147659.html

相关文章:

  • VPN网关
  • 产品展示视频制作的要点
  • appium+python自动化测试
  • 【AI辅助办公】PDF转PPT,移除水印
  • ssm农业视频实时发布管理系统源码
  • 【100天精通python】Day48:python Web开发_WSGI接口与使用
  • Understanding Lockup Cells
  • javaCV实现java图片ocr提取文字效果
  • 七牛云OSS存储
  • 11.物联网lwip,网卡原理
  • 视频监控/视频汇聚/视频云存储EasyCVR平台接入华为ivs3800平台提示400报错,该如何解决?
  • WordPress主题Zing V2.2.1/模块化WordPress响应式通用企业商城主题
  • 【无需公网IP】在树莓派上搭建Web站点
  • 出差在外,远程访问企业局域网象过河ERP系统「内网穿透」
  • Vue2-replace属性、编程式路由导航、缓存路由组件、两个新的生命周期钩子、路由守卫、路由器工作模式
  • C语言:指针的运算
  • 设计模式的使用——模板方法模式+动态代理模式
  • C++学习记录——삼십 智能指针
  • 插件式架构 与 ReSharper、Visual Studio的故事
  • Python UDP编程
  • 结构体(个人学习笔记黑马学习)
  • 小白带你学习linux的PXE装机
  • 华为鲲鹏服务器
  • Python金币小游戏
  • Modbus转Profinet网关在大型自动化仓储项目应用案例
  • Java 并发 ThreadLocal 详解
  • JWT 技术的使用
  • 机器学习深度学习——NLP实战(自然语言推断——微调BERT实现)
  • 如何在windows下使用masm和link对汇编文件进行编译
  • Golang字符串基本处理方法