1. 程式人生 > >golang 學習 ---- channel

golang 學習 ---- channel

把一個loop放在一個goroutine裡跑,我們可以使用關鍵字go來定義並啟動一個goroutine:

package main

import "fmt"

func loop() {
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", i)
	}
}

func main() {
	go loop() // 啟動一個goroutine
	loop()
}

輸出:

0 1 2 3 4 5 6 7 8 9

可是為什麼只輸出了一趟呢?明明我們主線跑了一趟,也開了一個goroutine來跑一趟啊。

原來,在goroutine還沒來得及跑loop的時候,主函式已經退出了。

main函式退出地太快了,我們要想辦法阻止它過早地退出,一個辦法是讓main等待一下:

package main

import (
	"fmt"
	"time"
)

func loop() {
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", i)
	}
}

func main() {
	go loop() // 啟動一個goroutine
	loop()
	time.Sleep(time.Second)
}

可是採用等待的辦法並不好,如果goroutine在結束的時候,告訴下主線說“Hey, 我要跑完了!”就好了, 即所謂阻塞主線的辦法,回憶下我們

Python裡面等待所有執行緒執行完畢的寫法:

for thread in threads:
    thread.join()

是的,我們也需要一個類似join的東西來阻塞住主線。那就是通道(channel)

channel是goroutine之間互相通訊的東西。類似我們Unix上的管道(可以在程序間傳遞訊息), 用來goroutine之間發訊息和接收訊息。其實,就是在做goroutine之間的記憶體共享。

使用make來建立一個通道:

var channel chan int = make(chan int)
// 或
channel := make(chan int)

那如何向通道存訊息和取訊息呢? 一個例子:

package main

import (
	"fmt"
)

func main() {
	var msg chan string = make(chan string)//無緩衝channel
	go func(message string) {
		msg <- message // 存訊息
	}("Ping!")

	fmt.Println(<-msg) // 取訊息
}

預設的,通道的存訊息和取訊息都是阻塞的 (叫做無緩衝的通道,不過緩衝這個概念稍後瞭解,先說阻塞的問題)。

也就是說, 無緩衝的通道在取訊息和存訊息的時候都會掛起當前的goroutine,除非另一端已經準備好。

比如以下的main函式和foo函式:

package main

var ch chan int = make(chan int)

func foo() {
    ch <- 0  // 向ch中加資料,如果沒有其他goroutine來取走這個資料,那麼掛起foo, 直到main函式把0這個資料拿走
}

func main() {
    go foo()
    <- ch // 從ch取資料,如果ch中還沒放資料,那就掛起main線,直到foo函式中放資料為止
}

那既然通道可以阻塞當前的goroutine, 那麼回到上一部分「goroutine」所遇到的問題「如何讓goroutine告訴主線我執行完畢了」 的問題來, 使用一個通道來告訴主線即可:

package main

import "fmt"

var complete chan int = make(chan int)

func loop() {
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", i)
	}

	complete <- 0 // 執行完畢了,發個訊息
}

func main() {
	go loop()
	<-complete // 直到執行緒跑完, 取到訊息. main在此阻塞住
}

如果不用通道來阻塞主線的話,主線就會過早跑完,loop線都沒有機會執行、、、

其實,無緩衝的通道永遠不會儲存資料,只負責資料的流通,為什麼這麼講呢?

  • 從無緩衝通道取資料,必須要有資料流進來才可以,否則當前線阻塞

  • 資料流入無緩衝通道, 如果沒有其他goroutine來拿走這個資料,那麼當前線阻塞

所以,你可以測試下,無論如何,我們測試到的無緩衝通道的大小都是0 (len(channel))

如果通道正有資料在流動,我們還要加入資料,或者通道乾澀,我們一直向無資料流入的空通道取資料呢? 就會引起死鎖

死鎖

一個死鎖的例子:

package main

func main() {
	ch := make(chan int)
	<-ch // 阻塞main goroutine, 通道c被鎖
}

執行這個程式你會看到Go報這樣的錯誤:

fatal error: all goroutines are asleep - deadlock!

何謂死鎖? 作業系統有講過的,所有的執行緒或程序都在等待資源的釋放。如上的程式中, 只有一個goroutine, 所以當你向裡面加資料或者存資料的話,都會鎖死通道, 並且阻塞當前 goroutine, 也就是所有的goroutine(其實就main線一個)都在等待通道的開放(沒人拿走資料通道是不會開放的),也就是死鎖咯。

我發現死鎖是一個很有意思的話題,這裡有幾個死鎖的例子:

只在單一的goroutine裡操作無緩衝通道,一定死鎖。比如你只在main函式裡操作通道:

package main

import "fmt"

func main() {
	ch := make(chan int)
	ch <- 1                                // 1流入通道,堵塞當前線, 沒人取走資料通道不會開啟
	fmt.Println("This line code wont run") //在此行執行之前Go就會報死鎖
}

主線等ch1中的資料流出,ch1等ch2的資料流出,但是ch2等待資料流入,兩個goroutine都在等,也就是死鎖

package main

import "fmt"

var ch1 chan int = make(chan int)
var ch2 chan int = make(chan int)

func say(s string) {
	fmt.Println(s)
	ch1 <- <-ch2 // ch1 等待 ch2流出的資料
}

func main() {
	go say("hello")
	<-ch1 // 堵塞主線
}

  總結來看,為什麼會死鎖?非緩衝通道上如果發生了流入無流出,或者流出無流入,也就導致了死鎖。或者這樣理解 Go啟動的所有goroutine裡的非緩衝通道一定要一個線裡存資料,一個線裡取資料,要成對才行 。所以下面的示例一定死鎖:

package main

func main() {
	c, quit := make(chan int), make(chan int)

	go func() {
		c <- 1    // c通道的資料沒有被其他goroutine讀取走,堵塞當前goroutine
		quit <- 0 // quit始終沒有辦法寫入資料
	}()

	<-quit // quit 等待資料的寫
}

  仔細分析的話,是由於:主線等待quit通道的資料流出,quit等待資料寫入,而func被c通道堵塞,所有goroutine都在等,所以死鎖。

修正死鎖
package main

func main() {
	c, quit := make(chan int), make(chan int)

	go func() {
		c <- 1    // c通道的資料沒有被其他goroutine讀取走,堵塞當前goroutine
		quit <- 0 // quit始終沒有辦法寫入資料
	}()

	go func() {
		<-c
		<-quit
	}()

}

給channel增加緩衝區,然後在程式的最後讓主執行緒休眠一秒,程式碼如下:

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int, 1)
	ch <- 1
	go func() {
		v := <-ch
		fmt.Println(v)
	}()
	time.Sleep(1 * time.Second)
	fmt.Println("2")

}

相關推薦

golang 學習 ---- channel

把一個loop放在一個goroutine裡跑,我們可以使用關鍵字go來定義並啟動一個goroutine: package main import "fmt" func loop() { for i := 0; i < 10; i++ { fmt.Printf("%d ", i) }

golang 學習 ---- channel

把一個loop放在一個goroutine裡跑,我們可以使用關鍵字go來定義並啟動一個goroutine: package main import "fmt" func loop() { for i := 0; i < 10; i++ { fmt.Printf("%d ", i) } }

Golang學習之Goroutines和 channel

Goroutines 在Go語言中,每一個併發的執行單元叫作一個goroutine,可以簡單地把goroutine類比作一個執行緒,本質上是一個協程。但它與協程有倆點不同: goroutinue可以實現並行,也就是說,多個協程可以在多個處理器同時跑。而協程同一

深入學習golang(2)—channel

Channel 1. 概述 “網路,併發”是Go語言的兩大feature。Go語言號稱“網際網路的C語言”,與使用傳統的C語言相比,寫一個Server所使用的程式碼更少,也更簡單。寫一個Server除了網路,另外就是併發,相對python等其它語言,Go對併發支援使得它有更好的效能。 Goroutine

golangchannel

microsoft 沒有 而已 創建 無緩沖 eight 不能 否則 false channel的機制是先進先出 無緩沖的channel: 如果你給channel賦值了,那麽必須要讀取它的值,不然就會造成阻塞。 chreadandwrite :=make

Golang學習

port hello 資源 god idea 歷史 函數多返回值 class func 一、為什麽是Go?   每隔一段時間就會出現不少的語言,我只是恰恰了解了這一門語言。我也是最近才接觸的。初略的學習了一下之後,覺得非常吸引我。吸引我的地方沒有別的就是go fmt。 二、

Golang學習 - strconv 包--數據類型轉換

graph str 中大 \ufeff 布爾 前綴 size 是否 int // 將布爾值轉換為字符串 true 或 false func FormatBool(b bool) string // 將字符串轉換為布爾值 // 它接受真值:1, t, T, TRUE, tr

Golang學習-第一篇 Golang的簡單介紹及Windows環境下安裝、部署

需要 簡單 電腦 pan 生成文件 多核 -- pear () 序言 這是本人博客園第一篇文章,寫的不到位之處,希望各位看客們諒解。 本人一直從事.NET的開發工作,最近在學習Golang,所以想著之前學習的過程中都沒怎麽好好的將學習過程記錄下來。深感惋惜! 現在將Gola

golang學習筆記(1):安裝&helloworld

golang安裝:golang編譯器安裝過程比較簡單,也比較快,不同平臺下(win/linux/macos)都比較相似;https://dl.gocn.io/golang/1.9.2/go1.9.2.src.tar.gz 下載對應的系統版本的編譯器go的版本號由"." 分為3部分如當前的

golang學習(一)

語言 搭建服務器 自定義 blog 發的 服務器 bsp 問題 希望 這周開始學習go語言,主要是web開發方面的。 來源:golang是爹爹是谷歌,2007年末開始開發,2009年11月開源 keys:開源,簡潔,安全,並行,高效 應用:搭載web服務器 學習小劇場:我本

golang檢視channel緩衝區的長度

golang提供內建函式cap用於檢視channel緩衝區長度。 cap的定義如下: func cap(v Type) int The cap built-in function returns the capacity of v, according to its type: - Array: th

Golang學習筆記(八)switch分支語句

Golang的switch可以不用在每個case裡寫一個break,Golang會自動加入。 default關鍵字可以帶,也可以不帶,不是必須要有的。 首先是一個最基礎的示例,在switch後面帶一個變數。 func ScoreGrade1() { gradel := "B" s

Golang學習筆記(七)if判斷語句

golang的判斷語句,不再需要用()來括起條件,但{必須跟if在一行。和java一樣,也有else關鍵字。 基礎例子,判斷奇偶數,給出一個值30,讓程式來判斷。 func EvenOdd(){ num := 30 if num % 2 == 0 { fmt.Println(nu

Golang學習筆記(六)運算子

實在是沒有什麼好寫的,寫幾個函式來體現幾個運算子。 新建一個go file,新增三個變數 var a = 21.0 var b = 5.0 var c float64 建一個函式來體現算術運算子 func Arithmetic() { c = a + b fmt.Printf

Golang學習筆記(五)常量及iota

Golang語言申明常量,需要用到一個關鍵字const。 const STR1 string = "hello" 大家習慣性的將常量設定為全大寫,但在Golang裡面是沒有private、public等許可權設定的,這些許可權僅靠方法、函式、變數等的首字母大小寫來設定,所以如果全大寫,將

Golang學習筆記(四)資料型別轉換

整數型別和浮點型別的轉換,先申明兩個變數,一個是int型的,一個是float型的。 chinese := 90 english := 80.9 將int型的強轉為float型,可以使用float32()或float64(),如float32(int型變數) avg1 := (floa

Golang學習筆記(三)列印格式化

通用列印格式化: str1 := "yoni" fmt.Printf("%T,%v \n", str1, str1) var a rune = '一' fmt.Printf("%T,%v \n", a, a) p := point{1,2} fmt.Printf("%T,%v \n",p,p

Golang學習筆記(二)資料型別

Go的資料型別與Java等語言的資料型別幾乎一致 //byte其實就是uint8的別名 var aaa byte = 100 // rune其實就是int32的別名 var bbb rune = 200 //可以給一個字元,計算ascll碼 var ddd byte = 'a' var c

Golang學習筆記(一)變數申明

第一種:指定變數型別,聲明後若不賦值,使用預設值。 var aaa int aaa = 10 第二種:根據值自行判定變數型別。 var aaa = "string" 第三種:用程式碼塊批量生成變數。 var ( a int b string c float32 d

18-golang通過channel實現斐波那契數列

    寫斐波那契數列其實很簡單 但是我們用channel來寫 自我鍛鍊一下 也熟悉一下channel的用法       func main() { channel := make(chan int)