go併發的基礎知識

  • Channel

    • 是阻塞同步的
    • 通過make創建,close關閉
    • Channel是引用類型
    • 可以使用for range 來迭代不斷操作channel
    • 可以設置單向或雙向通道
    • 可以設置緩存大小,在未被填滿前不會發生阻塞
  • Select

    • 可以處理一個或多個channel的發送與接收
    • 同時有多個可用的channel時按隨機順序處理
    • 可用空的select來阻塞main函數
    • 可設置超時

一個簡答的例子

...
func main(){
	go work()
}
func work(){
	fmt.Println("Go!")
}

輸入:
(什麼都沒有)

什麼都沒有輸出,原因是由代碼可以看出,啓動了一個攜程,在work函數還沒有打印主函數已經結束了。

爲了讓work裏的打印可以輸出,讓主線程睡1秒中,這是爲了讓攜程可以執行完畢,代碼修改如下:

...
func main(){
	go work()
	time.sleep(time.Second)
}
func work(){
	fmt.Println("Go!")
}

以上僅僅是爲了效果,在實際的開發中不會這樣做,因爲我們無法判斷work函數的執行邏輯到底需要多長時間。
藉助Channel,替換time.sleep

package main

import (
	"fmt"
)

func main() {
	c:= make(chan bool)
	go work(c)
	<- c //這裏會阻塞,直到c裏有值
}

func work(c chan bool){
	fmt.Println("Go Go Go!")
	c <- true
}

輸出:
Go Go Go !

注意:以上是個簡單的程序,只向channel放了一個值,當取出後,資源就會自動釋放了,程序就結束了,所以不用再調用close關閉通道了。

上面的程序從通道里取值,沒有賦值給任何變量也沒有輸出,如下使用range從通道里將值打印出來:

package main

import (
	"fmt"
)

func main() {
	c:= make(chan bool)
	go work(c)
	<- c

	for v:= range c{
		fmt.Println(v)
	}
}

func work(c chan bool){
	fmt.Println("Go Go Go!")
	c <- true
	close(c)
}

輸出:
Go Go Go!
true

以上程序修改了:11-13行使用range 將值打印出來, 19行調用了close函數關閉通道,如果不關閉通道,就會不斷的遍歷通道造成死鎖。
for i := range c能夠不斷的讀取channel裏面的數據,直到該channel被顯式的關閉。關閉channel之後就無法再發送任何數據了,在消費方可以通過語法v,ok := <-ch測試channel是否被關閉。如果ok返回false,那麼說明channel已經沒有任何數據並且已經被關閉。

設置有緩存的通道
c:= make(chan bool,1)
有緩存的就是channel可以存儲多少元素。ch:= make(chan bool, 4),創建了可以存儲4個元素的bool 型channel。在這個channel 中,前4個元素可以無阻塞的寫入。當寫入第5個元素時,代碼將會阻塞,直到其他goroutine從channel 中讀取一些元素,騰出空間。
有緩存和無緩存的區別:有緩存的是異步的,無緩存是同步阻塞的。如下示例:

package main

import (
	"fmt"
)

func main() {
	c:= make(chan bool,1)
	go func(){
		fmt.Println("Go Go Go!")
		<- c
	}()
	c <- true
}

以上程序不會有任何輸出,以上創建了有緩存的通道,在執行14行的時候,程序不會阻塞,就不會等待攜程的執行。

package main

import (
	"fmt"
)

func main() {
	c:= make(chan bool,1)
	go func(){
		fmt.Println("Go Go Go!")
		c <- true
	}()
	<- c
}

輸出:
Go Go Go!

緩存通道的例子

package main

import (
	"fmt"
	"runtime"
)

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	c := make(chan bool,10)
	for i := 0; i < 10; i++ {
		go Go(c, i)
	}
	for i := 0; i < 10; i++ {
		<-c
	}
}

func Go(c chan bool, index int) {
	a := 1

	for i := 0; i <= 10000000; i++ {
		a += i
	}
	fmt.Println(index, a)
	c <- true
}

實現以上程序的另一個辦法:

package main

import (
	"fmt"
	"sync"
)

func main() {
	wg:= sync.WaitGroup{}
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go Go(&wg, i)
	}
	wg.Wait()
}

func Go(wg *sync.WaitGroup, index int) {
	a := 1
	for i := 0; i <= 10000000; i++ {
		a += i
	}
	fmt.Println(index, a)
	wg.Done()
}

輸出:
0 50000005000001
5 50000005000001
9 50000005000001
1 50000005000001
7 50000005000001
6 50000005000001
3 50000005000001
2 50000005000001
4 50000005000001
8 50000005000001

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章