上篇:GO——學習筆記(八)
下篇:GO——學習筆記(十)
參考:
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md
示例代碼——go_8
https://github.com/jiutianbian/golang-learning/blob/master/go_8/main.go
并發
goroutine
大多數語言實現并發,通常通過多線程來實現,golang也類似,但有所不同,具體如下:goroutine是Go并行設計的核心,goroutine是協程,他的粒度要比線程小,可以認為是輕量級的線程,具體什么是協程,請看此網址 https://www.zhihu.com/question/20862617,
執行goroutine只需極少的棧內存,可同時運行成千上萬個并發任務。goroutine比thread更易用、更高效、更輕便。
goroutine 中通過 go 關鍵字來實現,代碼如下
func testgo() {
go sayhelloMan() //通過go來調用協程執行方法
go sayhelloWomen()
time.Sleep(time.Millisecond) //由于goroutine是協程,所以這邊如果不加主線程等待時間,main函數會直接結束,不會等待goroutine執行完畢
fmt.Println("結束")
}
func sayhelloMan() {
for i := 0; i < 5; i++ {
fmt.Println("你好,大兄弟")
time.Sleep(10)
}
}
func sayhelloWomen() {
for i := 0; i < 5; i++ {
fmt.Println("你好,大妹子")
time.Sleep(10)
}
}
請注意time.Sleep(time.Millisecond),這段代碼,如果不加入這段線程阻塞的方法,那么main函數將會很快執行完便退出了,新開的協程將不會繼續執行。
channel
golang中協程間的通信,通過channel來實現,通過make創建出特定的channel類型的值,來發送和接收數據,channel通過操作符<-來接收和發送數據,代碼如下:
func send(c chan int, i int) {
fmt.Println("發送消息:", i)
c <- i //將值塞到channle中,如果recive方法沒有執行,send方法所在線程將阻塞
}
func recive(c chan int) {
i := <-c
fmt.Println("接收消息:", i)//將值塞從channle中取出,如果send方法沒有執行,recive所在線程將阻塞
}
func testchannel() {
ch := make(chan int) //定義一個channle,用來接收傳遞的值
go send(ch, 10)
recive(ch)
}
我們上面代碼中定義的channle,是無緩沖的channel,無緩沖的channle中數據的存取都是互斥的。拿上面的例子來說,通過send方法,往ch中存入了一個值,如果recive方法沒有執行從ch取出值的操作,send所在線程將阻塞,知道recive方法調用。且recive方法,從ch取出值的時候,只有當send方法,往ch中存入了一個值的時候,才能執行,否則會一直等待。
當然,如果對無緩沖的channel只有存取值的操作的其中一個的話,都將會出現死鎖,報錯 fatal error: all goroutines are asleep - deadlock!
func testchannel() {
ch := make(chan int) //定義一個channle,用來接收傳遞的值
//go send(ch, 10) //如果注釋此行或者下面一行中的其中一行,都將會出現死鎖報錯 fatal error: all goroutines are asleep - deadlock!
recive(ch)
}
buffered channel
上面的例子中都是定義的無緩沖的channle,現在定義有緩沖的channle,定義格式如下
ch := make(chan type, value)
其實,無緩沖的channle,是value = 0,當value>1時,就是有緩沖的channle。當讀寫有緩沖的channle時,直到寫滿value個元素才阻塞寫入。
func testbufferchannel() {
//ch := make(chan int)
bufferchannel := make(chan int, 1) //定義緩沖為1的channel
bufferchannel <- 1 //可以直接往緩沖的channel中存值,只要小于1
//bufferchannel <- 2 //大于1,會報死鎖的錯誤
//ch <- 1 //對于無緩沖的channle直接賦值,會報死鎖的錯誤
fmt.Println(<-bufferchannel)
}
Range和Close
可以通過range像操作slice或者map一樣操作緩存類型的channel
func testrangeandclose() {
bufferchannel_1 := make(chan int, 10)
go setNumber(cap(bufferchannel_1), bufferchannel_1)
for i := range bufferchannel_1 { //通過range取緩沖的channel中的值
fmt.Println(i)
}
}
func setNumber(n int, c chan int) {
for i := 0; i < n; i++ {
c <- i
}
close(c) //通過close關閉channel
}
Select
如果存在多個channel的時候,我們可以通過select監聽channel上的數據流動。
select默認是阻塞的,只有當監聽的channel中有發送或接收可以進行時才會運行,當多個channel都準備好的時候,select是隨機的選擇一個執行的。
func testselect() {
ch := make(chan int) //定義一個channle,用來接收傳遞的值
done := make(chan int) //用來傳遞完畢的標識
go speaknumber(ch, done)
for {
select {
case i := <-ch: //當ch準備好值時
fmt.Println("speack:", i)
case <-done: //當done準備好值時
fmt.Println("speack:done")
return
}
}
}
func speaknumber(ch chan int, done chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
done <- 0
}
在select里面還有default語法,select其實就是類似switch的功能,default就是當監聽的channel都沒有準備好的時候,默認執行的(select不再阻塞等待channel)。
select {
case i := <-c:
// use i
default:
// 當c阻塞的時候執行這里
}
超時
對于上面描述過阻塞超時的情況,我們可以通過select來設置超時來避免.
func testovertime() {
ch := make(chan int) //定義一個channle,用來接收傳遞的值
go saynumber(ch)
for {
select {
case i := <-ch:
fmt.Println("say:", i)
case <-time.After(1 * time.Second):
fmt.Println("say:timeout")
return
}
}
}
func saynumber(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
}