主頁 > 後端開發 > 《Go 語言并發之道》讀書筆記(五)

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

2022-11-24 06:56:02 後端開發

今天這篇筆記我們來記錄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.

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/538151.html

標籤:其他

上一篇:遞回與Stream流轉換

下一篇:day22-web開發會話技術04

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more