《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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...