《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
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...