golang的chan有趣用法

2021-10-08 12:56:01 字數 2035 閱讀 4128

寫這個部落格的背景是我面試一家公司,這家公司的cto給我出了一道我認為挺有意思的題,題的大概是這樣的:

// 抽象乙個柵欄

type barrier inte***ce

// 建立柵欄物件

func newbarrier (n int) barrier

// 柵欄的實現類

type barrier struct

// 測試**

func main ()

}需要對上面的newbarrier()函式和barrier這個類進行修改,達到預期的效果。而且還要有條件約束,就是不能用任何同步相關的操作,但可以用chan,前提是無緩衝模式的。

在做這個問題的時候我一直在想c裡面有人實現無鎖佇列,利用的cpu的cas指令可以不用加鎖就可以對變數實現原子操作。先不說golang中是不是支援此類的語言,其實原子操作也會是同步操作的範疇,本身就已經犯規了,所以還是老老實實的用chan是實現吧。對chan有了解的都知道,沒有緩衝的生產和消費必須同時呼叫二者都會被喚醒,否則任意一方都會被阻塞。至少呼叫wait()阻塞的方法有乙個選擇了,那我們的先可以這麼實現:

type barrier struct // 所有呼叫wait()函式的先通過這個chan阻塞

count   int           // 記住數量,阻塞超過這個量就可以啟用所有協程了

}// 按照上面的定義,建構函式就可以這麼寫了

func newbarrier(n int) barrier ))}

}// wait()可以先寫成這樣

func (b *barrier) wait() {}

}上面的**至少實現了所有的協程都能阻塞了,那麼問題來了,我通過什麼方式計數呢?因為chan沒有緩衝,沒法用cap()函式獲取數量。如果定義全域性變數的方式計數,沒有鎖或者原子操作是沒法正確統計計數的,唯一的方式就是從chan中乙個乙個的讀出來計數。那麼就會增加如下**:

func newbarrier(n int) barrier ), chsync: make(chan struct{})}

go b.sync() // 增加乙個協程用來後台計數

return b

}// 後台計數的協程

func (b *barrier) sync() }}

現在我們面臨了新的問題,當後台這個協程每次從chan讀取乙個元素的時候,那個傳送該資料阻塞的協程就會被喚醒,這個就沒法滿足我們的要求了。有什麼方法能讓傳送協程不被啟用麼,如果是當前狀態的chan是無解的。那只能再想乙個方法就是讓那個寫入協程再次阻塞,起初我的想法可以用sleep()函式,後台協程累計計數達到條件後設定乙個標記,寫入協程通過迴圈sleep()一段時間判斷這個標記就可以搞定了。但是作為老司機的我根本沒法接受這麼醜陋的**,我再用乙個chan阻塞不就可以了麼?當統計計數滿足條件,直接close掉這個chan,所有的協程就自動啟用了,所以**就變成了這樣:

type barrier struct

chsync  chan struct{}     // 增加乙個chan

count   int

}func newbarrier(n int) barrier ), chsync: make(chan struct{})}

go b.sync()

return b

}func (b *barrier) wait() {}

<-b.chsync                // 再次阻塞

}func (b *barrier) sync() }}

完美解決問題,如果僅僅是解決這個問題我就不會寫這個文章了。在這個事情上我想到了兩個chan可以實現很多很有意思的功能,上面提到的問題就是其中之一。我再說乙個例子:設計乙個分發器,當有資料進入分發器後,需要將資料分發到多個處理器處理,每個處理器可以想象為乙個協程,處理器在沒有資料的時候要阻塞。我相信很多人肯定會設計成這樣:

這種設計的最大的問題在於多個processor處理時長不同會造成木桶效應,多個processor會被乙個processor拖累。那有人肯定會說可以給chan加緩衝啊,試問緩衝設計多大合適呢?如果真存在乙個處理非常慢的processor多大的緩衝都無濟於事,所以應該設計成這樣:

golang中chan型別的位址

chan型別本是就是指標,因此直接列印即可,不需要再取位址.如果在取位址就是 指向指標的指標 pointer to pointer 即類似c c 中的二級指標 如 int a int p a int pp p 其中pp就是二級指標 package main import fmt func main ...

Golang 關於通道 Chan 詳解

首先我們來看執行緒,在golang裡面也叫goroutine 下面我們先來看乙個例子吧 import fmt funcmain 在golang裡面,使用go這個關鍵字,後面再跟上乙個函式就可以建立乙個執行緒。後面的這個函式可以是已經寫好的函式,也可以是乙個匿名函式 funcmain i fmt.pr...

Golang 關於通道 Chan 詳解

首先我們來看執行緒,在golang裡面也叫goroutine 在讀這篇文章之前,我們需要了解一下併發與並行。golang的執行緒是一種併發機制,而不是並行。它們之間的區別大家可以上網搜一下,網上有很多的介紹。下面我們先來看乙個例子吧 import fmt funcmain 在golang裡面,使用g...