併發技術3:管道通訊

尹成發表於2018-11-16

#channel 介紹
channel 提供了一種通訊機制,通過它,一個 goroutine 可以想另一 goroutine 傳送訊息。channel 本身還需關聯了一個型別,也就是 channel 可以傳送資料的型別。例如: 傳送 int 型別訊息的 channel 寫作 chan int 。
#channel 建立
channel 使用內建的 make 函式建立,下面宣告瞭一個 chan int 型別的 channel:

ch := make(chan int)

c和 map 類似,make 建立了一個底層資料結構的引用,當賦值或引數傳遞時,只是拷貝了一個 channel 引用,指向相同的 channel 物件。和其他引用型別一樣,channel 的空值為 nil 。使用 == 可以對型別相同的 channel 進行比較,只有指向相同物件或同為 nil 時,才返回 true
#channel 的讀寫操作

ch := make(chan int)

// write to channel
ch <- 123

// read from channel
x := <- ch

// another way to read
x = <- chnnel 一定要初始化後才能進行讀寫操作,否則會永久阻塞。

channel 一定要初始化後才能進行讀寫操作,否則會永久阻塞。

#關閉 channel
golang 提供了內建的 close 函式對 channel 進行關閉操作。

ch := make(chan int)
close(ch)

有關 channel 的關閉,你需要注意以下事項:

  1. 關閉一個未初始化(nil) 的 channel 會產生 panic
  2. 重複關閉同一個 channel 會產生 panic
  3. 向一個已關閉的 channel 中傳送訊息會產生 panic
  4. 從已關閉的 channel 讀取訊息不會產生 panic,且能讀出 channel中還未被讀取的訊息,若訊息均已讀出,則會讀到型別的零值。從一個已關閉的 channel 中讀取訊息永遠不會阻塞,並且會返回一個為
  5. false 的 ok-idiom,可以用它來判斷 channel 是否關閉
  6. 關閉 channel 會產生一個廣播機制,所有向 channel 讀取訊息的 goroutine 都會收到訊息
ch := make(chan int, 10)
ch <- 11
ch <- 12

close(ch)

for x := range ch {
    fmt.Println(x)
}

x, ok := <- ch
fmt.Println(x, ok)


-----
output:

11
12
0 false

#channel 的型別
channel 分為不帶快取的 channel 和帶快取的 channel。

#無快取的 channel
從無快取的 channel 中讀取訊息會阻塞,直到有 goroutine 向該 channel 中傳送訊息;同理,向無快取的 channel 中傳送訊息也會阻塞,直到有 goroutine 從 channel 中讀取訊息。

通過無快取的 channel 進行通訊時,接收者收到資料 happens before 傳送者 goroutine 喚醒

#有快取的 channel
有快取的 channel 的宣告方式為指定 make 函式的第二個引數,該引數為 channel 快取的容量

ch := make(chan int, 10)

有快取的 channel 類似一個阻塞佇列(採用環形陣列實現)。當快取未滿時,向 channel 中傳送訊息時不會阻塞,當快取滿時,傳送操作將被阻塞,直到有其他 goroutine 從中讀取訊息;相應的,當 channel 中訊息不為空時,讀取訊息不會出現阻塞,當 channel 為空時,讀取操作會造成阻塞,直到有 goroutine 向 channel 中寫入訊息。

ch := make(chan int, 3)

// blocked, read from empty buffered channel
<- ch

上面的例子,沒人寫,讀取一直被阻塞

ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3

// blocked, send to full buffered channel
ch <- 4

上面的例子,寫入3個資料,耗盡快取能力,再寫就阻塞了

通過 len 函式可以獲得 chan 中的元素個數,通過 cap 函式可以得到 channel 的快取長度。

#例項
###通過channel實現同步

####匯入依賴

import (
	"fmt"
	"time"
)
//語法點①:建立int型別的無快取管道
//var ch = make(chan int)
var ch = make(chan int,0)

func Printer(str string) {
	for _, data := range str {
		fmt.Printf("%c", data)
		time.Sleep(time.Second)
	}
	fmt.Printf("\n")
}

func person1() {
	//列印完需要7秒鐘
	//勞資不列印完是不會往管道中塞資料的,阻塞不死你丫的
	Printer("今生註定我愛你")

	//箭頭指向管道內部,寫資料
	//在打完今生註定我愛你(耗時7秒鐘)後,才寫入資料
	//語法點②:向管道里寫資料,無論讀寫,箭頭只能朝左
	//語法點⑤:如果管道快取已滿,則阻塞等待至有人取出資料騰出空間,再寫入
	ch <- 666
}

func person2() {
	//箭頭指向管道外面,代表從管道中拿出資料,讀資料

	//語法點③:從管理取出資料,但不不接收
	//語法點⑥:管道里沒資料時,阻塞死等
	<-ch

	//語法點④:從管理取出資料,且使用data變數接收
	//data:=<-ch
	//fmt.Println("讀出資料:",data)

	//終於媽的可以列印了
	Printer("FUCKOFF")
}

func main() {

	go person1()
	go person2()

	//主協程賴著不死
	for {
		time.Sleep(time.Second)
	}
}

###通過channel實現同步和資料互動

package main

import (
	"fmt"
	"time"
)

func main() {
	//建立無快取管道
	ch := make(chan string)

	//5、主協程結束
	defer fmt.Println("主協程也結束")

	//子協程負責寫資料
	go func() {
		//3、結束任務
		defer fmt.Println("子協程呼叫完畢")

		//1、緩緩列印2次序號
		for i := 0; i < 2; i++ {
			fmt.Println("子協程 i= ", i)
			time.Sleep(time.Second)
		}

		//2、向管道傳送資料
		ch <- "我是子協程,工作完畢"
	}()

	//4、阻塞接收
	str := <-ch
	fmt.Println("str = ", str)
}

###無緩衝的channel

package main

import (
	"fmt"
	"time"
)

func main() {
	//建立一個無緩衝的管道
	ch := make(chan int, 1)

	//長度0,快取能力0
	fmt.Printf("len(ch) = %d, cap(ch)=%d\n", len(ch), cap(ch))

	go func() {
		//向管道中存入0,被阻塞,存入1,被阻塞,存入2
		for i := 0; i < 3; i++ {
			fmt.Println("子協程: i = ", i)
			ch <- i

			fmt.Println("5秒以內被列印出來給傑神100萬!")
		}
	}()

	//睡眠2秒
	time.Sleep(5 * time.Second)

	//讀取0,被阻塞,讀取1,被阻塞,讀取2
	for i := 0; i < 3; i++ {
		num := <-ch
		fmt.Println("num = ", num)
	}

}

###有快取的channel

package main

import (
	"fmt"
	"time"
)

func main() {
	//建立3快取的管道
	ch := make(chan int, 3)
	//長度0,快取能力3(即使沒人讀,也能寫入3個值)
	fmt.Printf("len(ch) = %d, cap(ch) = %d\n", len(ch), cap(ch))

	//一次性存入3個:012,3456789
	go func() {
		for i := 0; i < 10; i++ {
			ch <- i
			fmt.Printf("子協程存入[%d]: len(ch) = %d, cap(ch) = %d\n", i, len(ch), cap(ch))
			//time.Sleep(1 * time.Second)
		}
	}()

	//time.Sleep(5 * time.Second)

	//一次性讀取3個:012,345,678,9
	for i := 0; i < 10; i++ {
		num := <-ch
		fmt.Println("num = ", num)
	}
	time.Sleep(1*time.Nanosecond)
}

學院Go語言視訊主頁
https://edu.csdn.net/lecturer/1928

[清華團隊帶你實戰區塊鏈開發]

(https://ke.qq.com/course/344443?tuin=3d17195d)
掃碼獲取海量視訊及原始碼 QQ群:721929980
在這裡插入圖片描述

相關文章