《Go 語言併發之道》讀書筆記(五)

来源:https://www.cnblogs.com/dk168/archive/2022/11/23/16919372.html
-Advertisement-
Play Games

遞歸與Stream流轉換 今天寫一個很久以前一直不太會的,今天花了大量的時間進行研究處理,現將代碼解析於此 list轉為類中一個屬性為key,類實例為value的Map Map<String, List<OrgTreeVo>> orgMap = orgList.stream().filter(h - ...


今天這篇筆記我們來記錄Channel 和 Select, Go語言併發中Channel是goroutine傳遞數據的橋梁,是非常重要的一個工具。

定義Channel

雙向Channel

要定義一個channel很簡單,只需要在類型前面加上chan就可以了,
stringStream := make(chan string)
這樣就是定義和實例化了一個string 類型的雙向channel,
先來看一個Hello World的例子

func main() {

	stringStream := make(chan string)
	go func() {
		stringStream <- "Hello channels"
	}()

	fmt.Println(<-stringStream)

運行代碼控制台列印出“Hello channels”, 這個簡單的例子中我們定義了一個string類型的channel, 啟動一個goroutine, 往這個channel中寫入“Hello channels”, 主的goroutinue會讀取這個channel裡面的value, 讀取是阻塞的,如果我們把寫的代碼註釋掉“stringStream <- "Hello channels"”,程式運行會報死鎖,因為沒有誰會寫入了,它一直等待。

單向Channel

我們也可以聲明單向的channel,也就是只讀或者只寫的channel.
var receiveChan <-chan string //一個只讀的channel
var sendChan chan<- string //一個只寫的channel,
既然是只讀,那麼誰來給它寫入呢, 這裡其實還是需要一個雙向的channel,然後把雙向的channel賦值給單向channel,如
stringStream := make(chan string)
receiveChan = stringStream
sendChan = stringStream

只讀和只寫channel有什麼作用呢? 他們主要是用在方法的參數或者返回中,用戶看到這個chan是只讀的或者只寫的就明確了它的使用方法。 對於只讀的,我們實際上用個雙向的channel,然後寫入雙向channel後, 把雙向channel賦值給只讀的channel. 如下示例代碼

func main() {
	stringStream := make(chan string)
	go send(stringStream, "passed message")
	receive(stringStream)
}

func send(pings chan<- string, msg string) {
	fmt.Println("ping " + msg)
	pings <- msg
}

func receive(receiver <-chan string) {
	fmt.Println(<-receiver)
}

我們在send方法中知道pings是只寫的,不會讀取它
在receive方法中知道receiver是只讀的, 不會寫它

讀取和寫入Channel

上面例子我們一件看到讀就通過value := <-channel, 把channel中的數據讀出來, 寫就通過 channel<- value, 箭頭方向也比較明確,比較好理解。這裡再給個通過range讀取channel的方法
先看示例代碼

	intStream := make(chan int)

	go func() {
		defer close(intStream)
		for i := 1; i <= 5; i++ {
			intStream <- i
			fmt.Printf("writer %d \n", i)
		}
	}()

	for integer := range intStream {
		fmt.Printf("receive %v \n", integer)
	}

	fmt.Println(<-intStream)
	fmt.Println(<-intStream)

我們寫入了五個value到intStream裡面, 讀取的時候通過range我就不用知道這個次數了,通過for range 就都拿到了。 上面程式輸出結果如下:

writer 1 
receive 1 
receive 2
writer 2
writer 3
receive 3
receive 4
writer 4
writer 5
receive 5
0
0

結果比較有意思, receive 2 跑到writer3的前面去了, 我猜測是這個channel是阻塞的,寫入的時候,必須讀了才能再寫,讀到1以後,2就可以寫了,還沒有來得及列印writer, read就拿到了。所以感覺上receive跑到writer前面去了。
最後兩個00是我故意列印出來的,從關閉的channel也能拿到有返回的數據,如果想確定數據是不是正常寫入的,可以加上 value,ok := <- intStream, 判斷 ok 是true和false判斷是否是正常寫入的。

緩衝Channel

我們前面看到的例子,寫入數據到channel後,必須等別的goroutine讀到後才可以繼續寫,那麼如果我想寫入後繼續去乾別的,就需要用到緩衝Channel, 也就可以多寫幾個到channel。 如下示例代碼

	intStream := make(chan int, 2)
	go func() {
		defer close(intStream)
		defer fmt.Println("Producer Done")
		for i := 0; i < 5; i++ {
			intStream <- i
			fmt.Printf("Sending: %d \n", i)

		}
	}()

	time.Sleep(10 * time.Second)

	for i := 0; i < 5; i++ {
		v := <-intStream
		fmt.Printf("Received: %d \n", v)
		time.Sleep(1 * time.Second)
	}

定義一個緩衝區為2的channel, 當寫入兩個後會被阻塞。 輸出結果如下

Sending: 0 
Sending: 1 
Received: 0 
Sending: 2
Received: 1 
Sending: 3 
Received: 2 
Sending: 4 
Producer Done
Received: 3 
Received: 4 

可以看到當發送了兩個後,發送就阻塞起來了,直到讀取了之後,才可以繼續發送。
這裡有個疑問點作者說 make(chan int) 和 make(chan int, 0) 是等效的,我實際驗證效果也確實是一樣的,但是我想不應該是make(chan int, 1) 嗎?但是實際效果make(chan int, 1) 和make(chan int, 0) 確實不一樣。 我實驗了下,make(chan int, 1) 寫第二個的時候被阻塞,用
make(chan int, 0),寫一開始就會阻塞,直到開始讀了,寫才會成功。當然不是先寫後讀,只是一種相互的阻塞狀態。

Select

作者在書中寫道:“Select是一個具有併發性的Go語言最重要的事情之一, 在一個系統中兩個或者多個組件的交集中,可以在本地、單個函數、或者類型以及全局範圍內查找select語句綁定在一起的channel。除了連接組件之外,在程式的某些關鍵節點上, select 語句可以幫助安全地將channel與諸如取消、超時、等待、預設值之類的概率結合起來”。

單一channel select

先來看一個簡單的例子

	start := time.Now()
	c := make(chan interface{})
	go func() {
		time.Sleep(5 * time.Second)
		close(c)
	}()

	fmt.Print("Blocking on read ... \n")

	select {
	case <-c:
		fmt.Printf("Unblocked %v later. \n", time.Since(start))
	}

程式輸出結果如下, 等待5S後,關閉了channel, 阻塞結束。

Blocking on read ... 
Unblocked 5.0101794s later. 

上面是一個單一channel select的例子, 它等效於下麵的語句

if c == nil {
    block()
}
<- c

多個channel

接著我們看一個多個channel可用的例子, 我自己稍微改裝了一下上面的例子

	start := time.Now()
	c := make(chan interface{})
	c2 := make(chan int)
	go func() {
		time.Sleep(5 * time.Second)
		for i := 0; i < 3; i++ {
			c2 <- i
		}

		close(c)
	}()

	fmt.Print("Blocking on read ... \n")

loop:
	for {
		select {
		case <-c:
			fmt.Printf("Unblocked %v later. \n", time.Since(start))
			break loop
		case data := <-c2:
			fmt.Printf("C2 received %d,  %v later. \n", data, time.Since(start))
		}
	}

這裡有兩個case, 一個收到後會退出迴圈,一個會讀取channel裡面的數據,程式運行結果如下所示

Blocking on read ... 
C2 received 0,  5.0148217s later. 
C2 received 1,  5.0155568s later. 
C2 received 2,  5.0161642s later.
Unblocked 5.0167839s later.

我們寫入的3個數據都被讀取到了,並且關閉channel後退出了迴圈。

書中還列舉了一個當多個channel都可用的時候,Go 語言執行偽隨機選擇,

	c1 := make(chan interface{})
	close(c1)
	c2 := make(chan interface{})
	close(c2)

	var c1Count, c2Count int
	for i := 1000; i >= 0; i-- {
		select {
		case <-c1:
			c1Count++
		case <-c2:
			c2Count++
		}
	}

	fmt.Printf("c1Count:%d \nc2Count: %d \n", c1Count, c2Count)

程式運行結果如下

c1Count:483 
c2Count: 518

運行1000次,兩個case比較平均的執行

超時

我們來看一個超時的例子

	start := time.Now()
	c1 := make(chan interface{})
	select {
	case <-c1:
		fmt.Println("received c1.")
	case <-time.After(2 * time.Second):
		fmt.Printf("Timed out. after %v later. \n", time.Since(start))
	}

程式輸出
Timed out. after 2.0099758s later.
沒有程式寫入c1, 所以在等待2S後,執行了time out.

default

來看default的例子

	start := time.Now()
	var c1, c2 <-chan interface{}
	select {
	case <-c1:
		fmt.Println("received c1.")
	case <-c2:
		fmt.Println("received c2.")
	default:
		fmt.Printf("default after %v later. \n", time.Since(start))
	}

程式幾乎立刻執行了default, 輸出如下
default after 0s later.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 遇到了一個 Spring Boot 3 整合 MyBatis 的問題,然後解決了。當然,這其實不是個大問題,只是自己編碼時遇到了,然後總結總結分享一下。如果有遇到類似問題的,可以參考一下。 交代一下背景 最近在熟悉 Spring Boot 3 版本的代碼,開發過程中遇到了一些小坑,不過很快都解決了。 ...
  • 最近在學習做微服務的項目,在參考他人的微服務項目時,發現資料庫表所對應的實體類都會實現Serializable介面,以往做的項目中並沒有遇到過,也沒有實現過這個介面,所以好奇實體類為什麼需要實現該介面,在查閱相關博客後,進行了總結記錄 原文鏈接:【java】java實體類為什麼要實現Serializ ...
  • # 1.列表的格式 # [數據1,數據2,數據3,···] # 列表 可變數據類型 # 列表可以存儲多個數據,數據之間的逗號以英文逗號分隔 # 列表可以存儲不同類型數據,但一般存儲同一數據類型,這樣便於操作 # list_name = [] # 定義了一個空的列表 # 定義了一個有數據的列表 # 可 ...
  • 日常工作中 Map 絕對是我們 Java 程式員高頻使用的一種數據結構,那 Map 都有哪些遍歷方式呢?這篇文章阿粉就帶大家看一下,看看你經常使用的是哪一種。 通過 entrySet 來遍歷 1、通過 for 和 map.entrySet() 來遍歷 第一種方式是採用 for 和 Map.Entry ...
  • 由於博主有很多個python環境,如msys64的python,anaconda3的python和官網下載的python, 當我在vscode運行python,需要安裝對應的包時,用pip安裝,如下 安裝成功了,但是還是沒有找到 原因非常簡單,就是我vscode使用的python環境不是上面那個py ...
  • new ,delete 運算符 int *p =new int; delete p; 看一下彙編代碼 可以看到new 和delete 運算符其實也是 operator運算符重載函數的調用 malloc和new malloc 按位元組開闢記憶體 new在開闢記憶體的時候需要指定類型 new int[10] ...
  • 一.小結 1.迴圈語句有三類:while迴圈,do-while迴圈和for迴圈 2.迴圈中需要重覆執行的語句所構成的整體稱為迴圈體 3.迴圈體執行一次稱為迴圈的一次迭代 4.無限迴圈是指迴圈語句被無限次執行 5.在設計迴圈時,既需要考慮迴圈控制構體,還需要考慮迴圈體 6.while迴圈首先檢查迴圈繼 ...
  • WEB開發會話技術04 14.Session生命周期 14.1生命周期說明 public void setMaxInactiveInterval(int interval):設置session的超時時間(以秒為單位),超過指定的時長,session就會被銷毀。 值為正數的時候,設置session的超 ...
一周排行
    -Advertisement-
    Play Games
  • 簡介 本文的初衷是希望幫助那些有其它平臺視覺演算法開發經驗的人能快速轉入Halcon平臺下,通過文中的示例開發者能快速瞭解一個Halcon項目開發的基本步驟,讓開發者能把精力完全集中到演算法的開發上面。 首先,你需要安裝Halcon,HALCON 18.11.0.1的安裝包會放在文章末尾。安裝包分開發和 ...
  • 本文是對Datawhale的動手學數據分析課程的學習總結,記錄了整體的學習過程、答案以及個人感想,代碼較為詳細。 ...
  • JZ7重建二叉樹 描述 給定節點數為 n 的二叉樹的前序遍歷和中序遍歷結果,請重建出該二叉樹並返回它的頭結點。 例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6} 提示: 1.vin.length == pre.length 2.pre 和 vin ...
  • 我們都知道在Java編程中多線程的同步使用synchronized關鍵字來標識,那麼這個關鍵字在JVM底層到底是如何實現的呢。 我們先來思考一下如果我們自己實現的一個鎖該怎麼做呢: 首先肯定要有個標記記錄對象是否已經上鎖,執行同步代碼之前判斷這個標誌,如果對象已經上鎖線程就阻塞等待鎖的釋放。 其次要 ...
  • 目錄 一.OpenGL 色階 1.Windows OpenGL ES 版本 2.Windows OpenGL 版本 二.OpenGL 色階 GLSL Shader 三.猜你喜歡 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎 零基礎 Ope ...
  • 1. 查看Linux伺服器版本信息 # cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) 2. 禪道開源版安裝包下載 wget http://dl.cnezsoft.com/zentao/9.8.2/ZenTaoPMS.9.8. ...
  • Spring 框架可以為 Java 應用程式開發提供全面的基礎設施支持,它是現在非常流行的 Java 開源框架,對於一個 Java 開發人員來說,熟練掌握 Spring 是必不可少的。 ...
  • 前言 本篇是c++總結的第二篇,關於c++的對象模型,在構造、拷貝虛函數上重點分析,也包含了c++11class的新用法和特性,如有不當,還請指教! c++三大特性 訪問許可權 ​ 在c++中通過public、protected、private三個關鍵字來控製成員變數和成員函數的訪問許可權,它們分別表示 ...
  • 一.小結 1.使用二維數組來存儲表格 2.可以使用以下語法來聲明二維數組變數: 元素類型[ ] [ ]數組變數 3.可以使用以下語法來創建二維數組變數: new 元素類型 [行的個數][列的個數] 4.使用下麵的語法表示二維數組中的每個元素: 數組變數[行下標][列的個數] 5.可使用數組初始化語法 ...
  • typimg是一款為typora編輯器提供圖像自定義上傳服務的工具,該工具將在typora中輸入的網路圖片、本地圖片、剪貼板圖片/截圖上傳到博客園,支持在MacOS、Windiws、Linux三個平臺上運行。 ...