本文由DeepL翻译, 原文见末尾
简介
当我第一次开始使用Go的通道时,我犯了一个错误,就是把通道当成了一种数据结构。我把通道看作是一个队列,在goroutine之间提供自动的同步访问。这种结构性的理解导致我写了很多糟糕的、复杂的并发代码。
随着时间的推移,我了解到最好是忘记通道的结构,而专注于它们的行为方式。所以现在当谈到通道时,我只考虑一件事:信号传递。一个通道允许一个goroutine向另一个goroutine发出关于一个特定事件的信号。信号传递是你应该用通道做的一切事情的核心。把通道看成是一种信号机制,可以让你写出更好的代码,有很好的定义和更精确的行为。
为了理解信号的作用,我们必须理解它的三个属性:
- 交付保证
- 状态
- 有或没有数据
这三个属性共同创造了一个围绕信号传递的设计理念。在我讨论了这些属性之后,我将提供一些代码例子,展示应用这些属性的信令。
交付保证(Guarantee Of Delivery)
交付保证是基于一个问题。"我是否需要保证某一个goroutine发送的信号已经被收到?"
换句话说,鉴于列表1中的这个例子。
Listing 1
01 go func() {
02 p := <-ch // Receive
03 }()
04
05 ch <- "paper" // Send
发送的goroutine是否需要保证在第05行的通道上发送的文件被第02行的goroutine收到,然后才继续前进?
根据这个问题的答案,你将知道应该使用哪种类型的通道。无缓冲的或有缓冲的。每种通道都围绕着交付的保证提供了不同的行为。
图1:交付保证
担保很重要,而且,如果你不这么认为,我有一大堆东西想卖给你。当然,我是想开个玩笑,但是当你在生活中没有保证的时候,你不会感到紧张吗?在编写并发软件时,对你是否需要担保有一个深刻的理解是至关重要的。随着我们的继续,你将学会如何决定。
状态
一个通道的行为直接受到其当前状态的影响。一个通道的状态可以是nil、开放或关闭。
下面的清单2显示了如何声明或放置一个通道到这三种状态中的每一种。
清单2
// ** nil channel
// A channel is in a nil state when it is declared to its zero value
var ch chan string
// A channel can be placed in a nil state by explicitly setting it to nil.
ch = nil
// ** open channel
// A channel is in a open state when it’s made using the built-in function make.
ch := make(chan string)
// ** closed channel
// A channel is in a closed state when it’s closed using the built-in function close.
close(ch)
状态决定了发送和接收操作的行为方式。
信号是通过一个通道发送和接收的。不要说读/写,因为通道不执行I/O。
图 2 : 状态
当一个通道处于无状态时,在该通道上尝试的任何发送或接收都将被阻止。当一个通道处于开放状态时,信号可以被发送和接收。当一个通道被置于关闭状态时,信号不能再被发送,但仍有可能接收信号。
这些状态将为你遇到的不同情况提供你需要的不同行为。当把状态与交付保证结合起来时,你可以开始分析由于你的设计选择而产生的成本/效益。在许多情况下,你也能通过阅读代码迅速发现错误,因为你了解通道将如何表现。
有数据和无数据
最后一个需要考虑的信号属性是你是否需要用数据发出信号。
你通过在一个通道上进行发送来发出带数据的信号。
清单3
01 ch <- "paper"
当你用数据发出信号时,通常是因为。
一个goroutine被要求开始一个新的任务。
一个goroutine报告了一个结果。
你通过关闭一个通道发出没有数据的信号。
清单4
01 close(ch)
当你没有数据信号时,通常是因为。
- 一个goroutine被告知要停止他们正在做的事情。
- 一个goroutine报告说他们已经完成了,但没有结果。
- 一个goroutine报告说它已经完成了处理并关闭了。
- 这些规则也有例外,但这些是主要的用例,也是我们在这篇文章中要关注的。我认为这些规则的例外情况是一种最初的代码气味。
无数据信号的一个好处是一个goroutine可以同时向许多goroutine发出信号。有数据的信号传递在goroutine之间总是1对1的交换。
用数据传递信号
当你要用数据发出信号时,有三个通道配置选项,你可以根据你需要的保障类型来选择。
图3:带数据的信令
三个通道选项是未缓冲、缓冲>1或缓冲=1。
- 担保
- 无缓冲通道给你一个保证,即正在发送的信号已经被接收。
- 因为信号的接收发生在信号的发送完成之前。
- 无担保
- 缓冲通道的大小>1,不能保证正在发送的信号已经被接收。
- 因为信号的发送发生在信号的接收完成之前。
- 延迟担保
- 一个大小=1的缓冲通道给你一个延迟的保证。它可以保证之前发送的信号已经被接收。
- 因为第一个信号的接收,发生在第二个信号的发送完成之前。