《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
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...