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

Remember發表於2021-05-27

介紹

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

不過這篇文章不會涉及到鳥窩自己實現的 chanx,我們會在下一篇提到。

我們都知道,channel 有兩種型別:無緩衝和有緩衝的。

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

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

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

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

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

比如有人提到:

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

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

針對這類需求,有很多版本的實現,我們來看其中的一個實現。鳥窩的 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 是咋麼改進的?

下一篇

參考

github.com/golang/go/issues/20352

colobu.com/2021/05/11/unbounded-ch...

medium.com/capital-one-tech/buildi...

本作品採用《CC 協議》,轉載必須註明作者和本文連結
吳親庫裡

相關文章