03.并发编程

Go 并发编程

Go 语言正是在多核和网络化的时代背景下诞生的原生支持并发的编程语言。Go 语言并发体系的理论是 C.A.R Hoare 在 1978 年提出的 CSP(Communicating Sequential Process,通讯顺序进程)。CSP 有着精确的数学模型,并实际应用在了 Hoare 参与设计的 T9000 通用计算机上。从 NewSqueak、Alef、Limbo 到现在的 Go 语言,对于对 CSP 有着 20 多年实战经验的 Rob Pike 来说,他更关注的是将 CSP 应用在通用编程语言上产生的潜力。

作为 Go 并发编程核心的 CSP 理论的核心概念只有一个:同步通信。在并发编程中,对共享资源的正确访问需要精确的控制,在目前的绝大多数语言中,都是通过加锁等线程同步方案来解决这一困难问题,而 Go 语言却另辟蹊径,它将共享的值通过 Channel 传递(实际上多个独立执行的线程很少主动共享资源)。在任意给定的时刻,最好只有一个 Goroutine 能够拥有该资源。数据竞争从设计层面上就被杜绝了。为了提倡这种思考方式,Go 语言将其并发编程哲学化为一句口号:

Do not communicate by sharing memory; instead, share memory by communicating.

Go 不推荐使用共享内存的方式传递数据,而推荐使用 Channel(或称“通道”)在多个 goroutine 之间传递数据,同时保证整个过程的并发安全性。不过,作为可选方法,Go 依然提供了一些传统的同步方法(比如互斥量、条件变量等)。

Go 的并发哲学

CSP 是 Go 并发重要的理论基石,而同时 Go 还支持通过内存访问同步和遵循该技术的基元来编写并发代码的更传统手段;同步和其他软件包中的结构和方法允许你执行锁定,创建资源池,抢占 goroutine 等。这就让我们在处理很多问题时面临选择,是应该使用通道还是传统锁。

并发的选择

  • 你想转移数据的所有权吗?

如果你有一些代码能够产生结果,并希望与另一部分代码共享这个结果,那么你真正在做的是转移那些数据的所有权。如果你熟悉不支持垃圾回收的语言的内存所有权概念,那么也可以称之为:数据所有者,使并发程序安全的一种方法是确保只有一个并发上下文拥有数据的所有权 。通道可以帮助我们来传达这一概念。

这样做的一大好处是你可以创建缓冲通道来实现资源廉价的内存队列,从而将你的生产者与消费者分离。另一个是通过使用通道,你可以隐式地将你的并发代码与其他并发代码组合在一起。

  • 你是否试图保护结构的内部状态?

这是内存访问同步原语的一个很好的选择,也是一个非常强大的指示器,你不应该使用通道。通过使用内存访问同步原语,你可以隐藏从呼叫者锁定关键部分的实现细节,但不会给调用者带来复杂性。这是一个线程安全类型的小例子:

Links

  • 2017-Concurrency in Go 中文笔记》📚: 以希望大家能够了解并掌握有关 Go 中并发性的高质量、全面的信息:如何使用它,如何将最佳实践和模式整合到系统中,以及它们如何在所有系统中运行。我尽力在这些考量之间取得平衡。TODO!