Golang Channel

channels 是一種類型安全的消息隊列, 充當兩個 goroutine 之間的管道, 將通過它同步的進行任意資源的交換. chan 控制 goroutines 交互的能力從而創建了 Go 同步機制. 當創建的 chan 沒有容量時, 稱為無緩衝channel.反過來, 使用容量創建的 chan 稱為緩衝channel. 要了解通過 chan 交互的 goroutine 的同步行為是什麼, 我們需要知道channel的類型和狀態. 根據我們使用的是無緩衝channel還是緩衝channel, 場景會有所不同, 所以讓我們單獨討論每個場景.

Unbuffered Channels

ch := make(chan struct{}) 無緩衝 chan 沒有容量, 因此進行任何交換前需要兩個 goroutine 同時準備好.當 goroutine 試圖將一個資源發送到一個無緩衝的通道並且沒有goroutine 等待接收該資源時, 該通道將鎖住發送 goroutine 並使其等待.當 goroutine 嘗試從無緩衝通道接收, 並且沒有 goroutine 等待發送資源時, 該通道將鎖住接收 goroutine 並使其等待. 無緩衝信道的本質是保證同步. unbuffered channel

第一個 goroutine 在發送消息 foo 之後被阻塞, 因為還沒有接收者準備好. 規範中對這種行為進行了很好的解釋:https://golang.org/ref/spec#Channel_types

"If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready."

https://golang.org/doc/effective_go.html#channels "If the channel is unbuffered, the sender blocks until the receiver has received the value"

  • Receive 先於 Send 發生.
  • 好處: 100% 保證能收到.
  • 代價: 延遲時間未知.
package main

import (
    "fmt"
    "sync"
    "time"
)

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)
        fmt.Println(`Message:` + <-c)
    }()

    wg.Wait()
}

/*
Message:foo
*/

Buffered Channels

buffered channel 具有容量, 因此其行為可能有點不同. 當 goroutine 試圖將資源發送到緩衝通道, 而該通道已滿時, 該通道將鎖住 goroutine並使其等待緩衝區可用. 如果通道中有空間, 發送可以立即進行, goroutine 可以繼續. 當goroutine 試圖從緩衝通道接收數據, 而緩衝通道為空時, 該通道將鎖住 goroutine 並使其等待資源被發送.

bufferedchannel

package main

import (
    "fmt"
    "sync"
    "time"
)

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)
        fmt.Println(`Message:` + <-c)
        fmt.Println(`Message:` + <-c)
    }()

    wg.Wait()
}

Latencies due to under-sized buffer

我們在 chan 創建過程中定義的緩衝區大小可能會極大地影響性能。 我將使用密集使用 chan 的扇出模式來查看不同緩衝區大小的影響。 在我們的基準測試中,一個 producer 將在通道中註入百萬個整數元素, 而5個 worker 將讀取並將它們追加到一個名為 total 的結果變量中。 latencies_sized_channel1 latencies_sized_channel2 latencies_sized_channel3

  • Send 先於 Receive 發生。
  • 好處: 延遲更小。
  • 代價: 不保證數據到達,越大的 buffer,越小的保障到達。 buffer = 1 時,給你延遲一個消息的保障。
  • 因為 unbuffer channel 的 goroutine 會不斷喚醒睡眠, context switch. 所以比 buffer channel 還慢.

Overlord是嗶哩嗶哩基於Go語言編寫的memcache和redis&cluster的代理及集群管理功能,致力於提供自動化高可用的緩存服務解決方案 https://github.com/bilibili/overlord

Go Concurrency Patterns

https://go.dev/blog/concurrency-timeouts https://blog.golang.org/pipelines https://talks.golang.org/2013/advconc.slide#1 https://github.com/go-kratos/kratos/tree/master/pkg/sync

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.
  • 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
  • Less is more with buffers.
    • (不要把buffer size當作效能的提升, 他只是 latencies變小, 能緩衝的數據變多了, 吞吐要靠多個goroutine來消費)
    • 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.

The Channel Closing Prirciple

  • 在沒有修改channel的狀態時, 此時沒有一個簡單通用的方式可以去檢查channel 是否關閉
  • 關閉一個 close的 channel 會發生 panic.
  • 將值發送到一個關閉的 channel 會發生 panic

  • 不要在接收端關閉 channel

  • 如果有多個發送端, 也不要在發送端關閉 channel
  • 只有一個發送端, 才在發送端關閉 channel
  • 不要關閉已關閉的 channel
  • 不要送值到已關閉的 channel
  • 有方向的 channel 不可被關閉
No Situation dataCh stopCh toStop closing closed middle layer
01 M Receivers 1 Sender 從 Sender 關閉 x x x x x
02 1 Receiver N Senders 不必關閉 Receiver: Close(stopCh) x x x x
03 M Receivers N Senders 不必關閉 引用 moderator: 角色來關閉額外的訊號通到 close(stopCh) 能讓任何 receiver 和 sender 閉數據通道 -> 發送訊號到 toStop channel
select {case toStop <- "sender#" + id:default:}
x x x
04 M Receivers 1 Sender Closed By 3Party Sender 收到 stop() 發出的 closing 信號後, close(dataCh) x x 第三方 goroutine call stop() function
stop := func() {select {case closing <- struct{}{}:<-closedcase <-closed:}}
Sender 收到 stop() 發出的 closing 信號 -> return -> run defer function close(closed);close(dataCh); x
05 M Receivers N Senders Closed By 3Party 在 middle layer close(dataCh) x x third party 呼叫 stop function: 發出 closing <- by 在 middle layer close(closed) 需要藉由 middle layer 將 N 個 Sender, 轉換成 1 個 sender 讓此情境變成類似成 4.1: M receivers and 1 Sender, 由sender close dataCh channel

Reference

© Kimi Tsai all right reserved.            Updated : 2023-07-12 09:04:53

results matching ""

    No results matching ""

    results matching ""

      No results matching ""