Go语言实践(五):Channels
2020-12-05 16:43:06channels 是一种类型安全的消息队列,充当两个 goroutine 之间的管道,将通过它同步的进行任意资源的交换。chan 控制 goroutines 交互的能力从而创建了 Go 同步机制。当创建的 chan 没有容量时,称为无缓冲通道。反过来,使用容量创建的 chan 称为缓冲通道。
要了解通过 chan 交互的 goroutine 的同步行为是什么,我们需要知道通道的类型和状态。根据我们使用的是无缓冲通道还是缓冲通道,场景会有所不同,所以让我们单独讨论每个场景。
Unbuffered Channels
ch := make(chan struct{})
无缓冲 chan 没有容量,因此进行任何交换前需要两个 goroutine 同时准备好。当 goroutine 试图将一个资源发送到一个无缓冲的通道并且没有goroutine 等待接收该资源时,该通道将锁住发送 goroutine 并使其等待。当 goroutine 尝试从无缓冲通道接收,并且没有 goroutine 等待发送资源时,该通道将锁住接收 goroutine 并使其等待。
无缓冲信道的本质是保证同步。
func main() {
c:= make(chan string)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
c <- `foo`
}
go func() {
defer wg.Done()
time.Sleep(time.Second * 1)
printIn(`Message` + <-c)
}()
wg.Wait()
}
第一个 goroutine 在发送消息 foo 之后被阻塞,因为还没有接收者准备好。规范中对这种行为进行了很好的解释:
- Receive 先于 Send 发生。
- 好处: 100% 保证能收到。
- 代价: 延迟时间未知。
Buffered Channels
buffered channel 具有容量,因此其行为可能有点不同。当 goroutine 试图将资源发送到缓冲通道,而该通道已满时,该通道将锁住 goroutine并使其等待缓冲区可用。如果通道中有空间,发送可以立即进行,goroutine 可以继续。当goroutine 试图从缓冲通道接收数据,而缓冲通道为空时,该通道将锁住 goroutine 并使其等待资源被发送。
func main() {
c:= make(chan string, 2)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
c <- `foo`
c <- `bar`
}
go func() {
defer wg.Done()
time.Sleep(time.Second * 1)
printIn(`Message` + <-c)
printIn(`Message` + <-c)
}()
wg.Wait()
}
Latencies due to under-sized buffer
我们在 chan 创建过程中定义的缓冲区大小可能会极大地影响性能。我将使用密集使用 chan 的扇出模式来查看不同缓冲区大小的影响。在我们的基准测试中,一个 producer 将在通道中注入百万个整数元素,而5个 worker 将读取并将它们追加到一个名为 total 的结果变量中。
- Send 先于 Receive 发生。
- 好处: 延迟更小。
- 代价: 不保证数据到达,越大的 buffer,越小的保障到达。buffer = 1 时,给你延迟一个消息的保障。
Go Concurrency Patterns
- Timing out
- Moving on
- Pipeline
- Fan-out, Fan-in
- Cancellation
- Close 先于 Receive 发生(类似 Buffered)。
- 不需要传递数据,或者传递 nil。
- 非常适合去掉和超时控制。
- Contex
Design Philosophy
- If any given Send on a channel CAN cause the sending goroutine to block:
- Not allowed to use a Buffered channel larger than 1.
- Buffers larger than 1 must have reason/measurements.
- Must know what happens when the sending goroutine blocks.
- Not allowed to use a Buffered channel larger than 1.
- If any given Send on a channel WON’T cause the sending goroutine to block:
- You have the exact number of buffers for each send.
- Fan Out pattern
- You have the buffer measured for max capacity.
- Drop pattern
- You have the exact number of buffers for each send.
- Less is more with buffers.
- Don’t think about performance when thinking about buffers.
- Buffers can help to reduce blocking latency between signaling.
- Reducing blocking latency towards zero does not necessarily mean better throughput.
- If a buffer of one is giving you good enough throughput then keep it.
- Question buffers that are larger than one and measure for size.
- Find the smallest buffer possible that provides good enough throughput.