什麼?無限緩衝的隊列(一)?

介紹

事情的原由是前幾周看到鳥窩寫了一篇關於實現無限緩衝 channel 的文章,當時忙着和小姐姐聊天沒看,今天想起來了。數組

不過這篇文章不會涉及到鳥窩本身實現的 chanx,咱們會在下一篇提到。app

咱們都知道,channel 有兩種類型:無緩衝和有緩衝的。函數

當咱們建立一個有緩衝的通道並指定了容量,那麼在這個通道的生命週期內,咱們將再也沒法改變它的容量。oop

有時候,咱們並不知道也沒法預估寫入通道的數量規模。若是此時通道的寫入速度遠遠超過讀取速度,那麼必然會在某個時間點塞滿通道,致使寫入阻塞。
好比以前我翻譯的一篇文章 使用 Go 每分鐘處理百萬請求 中,做者就出現處理速度太慢,致使通道塞滿,其餘請求被阻塞,響應時間慢慢增長。post

此時有人就會提到,能不能提供一個無限緩衝(Unbounded or Unlimited)的通道。測試

這個問題早在 2017 年就有人提過 issues,最終 go 官方沒有實現這個提案。this

不過,這個 issues 下面總共產生了 67 個 comments,評論很精彩。
imagespa

好比有人提到:翻譯

cznic:Unlimited capacity channels ask for a machine with unlimited memory.

rsc:The limited capacity of channels is an important source of backpressure in a set of communicating goroutines. It is typically a mistake to use an unbounded channel, because you lose that backpressure. If one goroutine falls sufficiently behind, you usually want to take some action in response, not just queue its messages forever. The appropriate response varies by situation: maybe you want to drop messages, maybe you want to keep summary messages, maybe you want to take different responses as the goroutine falls further and further behind. Making it trivial to reach for unbounded channels keeps developers from thinking about this, which I believe is a strong disadvantage.

那麼如何實現一個無限緩衝的通道呢?code

針對這類需求,有不少版本的實現,咱們來看其中的一個實現。鳥窩的 chanx 就是在這個基礎上作修改的。

咱們一步步還原它的實現,這其中還能知道做者的思考過程。

代碼

初版,

image

MakeInfinite 函數返回兩個通道,第一個用於數據的寫入,第二個用於數據的讀取。

注意看這裏的細節,在返回的時候就約束了通道的操做類型:一個只寫,一個只讀,這樣避免了用戶破壞通道的操做流程。
這裏面的代碼也簡單,只要寫入通道 in 未被關閉,那麼就把從 in 通道中讀取的值 appendinQueue 切片中。
inQueue 在這裏就是實現無限緩衝的中間層。

而後有個 test。

image

image

當走到第二個 case 的時候,因爲 inQueue 一開始是空的,那麼必然會出現 index out
不只是一開始,在運行中,若是讀取比寫入快,那麼必然也會致使相同的狀況。

image
image

inQueue 沒有值的時候,咱們把 nil 也寫入到通道,
而後測試代碼中咱們從 out channel 讀取數值試圖把值斷言 int 失敗了。 那麼,當隊列中沒有數據時,咱們不該該寫入 out 通道。

image
做者使用了一個技巧,若是 inQueue 沒有數據,那麼嘗試寫入一個 nil 通道將永遠阻塞。
一般,永久阻塞是一個很差的行爲,可是這個是包含在 select 語句中的,因此問題不大。

image

還有問題。緣由很簡單,咱們再發送完數據就立刻關閉了 in 通道。隨後 break loop。接下來關閉 out 通道,程序運行結束。
此時 inQueue 還有值未被取出。

只要寫比讀快,那麼就永遠存在這個問題。咱們須要保證在通道關閉的時候,inQueue 已爲空。
image

總結

上面是如何實現一個無限緩衝的 channel

藉助了一個臨時存儲數據的中間層。

上面的實現有沒有哪些地方能夠改進?

inQueue 做爲中間層,本質上是一個切片。明明 inQueue 已經擴容到很大的值了,可是並無對應的 reset。會致使 inQueue 指向還在底層數組靠後的位置,並不能複用數組前面的空間,形成浪費。

chanx 是咋麼改進的?

下一篇