是否關閉管道 | 是否有緩衝 | 讀取次數 ?寫入次數 | 現象 |
---|---|---|---|
是 | 是 | > | 讀取次數多於部分讀到的值爲管道數據結構的默認值 |
是 | 是 | < | 讀到多少算多少,多的值讀不了了 |
是 | 是 | = | 正常讀取 |
否 | 是 | > | 死鎖 |
否 | 是 | < | 不會死鎖,讀到多少算多少,多的值讀不了了 |
否 | 是 | = | 正常讀取 |
//deadlock func deadlockCase() { //無緩衝的信道在取消息和存消息的時候都會掛起當前的goroutine, //而當前只有1個main的線程(也是一個goroutine), //因此在進行存放數據的ch <- 1這一行,就會產生鎖,致使後邊代碼沒法執行。 //而整個程序無其它線程來讓這個鎖釋放,天然就造成了一個死鎖。 c := make(chan int) c <- 1 close(c) for i := range c { fmt.Println(i) } }
An emptyselect{}
statement blocks indefinitely i.e. forever. It is similar to an emptyfor{}
statement.
On most (all?) supported Go architectures, the empty select will yield CPU. An empty for-loop won't, i.e. it will "spin" on 100% CPU.
package main import ( "fmt" "sync" ) func main() { testSlice := []string{"test1", "test2", "test3"} wg := sync.WaitGroup{} for _, t := range testSlice { wg.Add(1) go printSlice(t, &wg) } wg.Wait() } func printSlice(s string, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("this is %+v\n", s) }
sync包實現併發的核心就是waitgroup,初始化一個WaitGroup類型的指針,須要併發時,則使用Add方法,添加計數,並在須要進行併發的函數中將其做爲參數傳入,當這個函數操做完成後,調用Done方法減掉計數;在主協程中,Wait方法會一直監聽計數器的數量,當計數器爲0時,說明全部的併發函數都完成了,這時主協程就能夠退出了;我的感受這是最簡單的實現併發的方式,在須要併發處理一個集合內的全部數據時尤爲好用;
管道是go原生支持的數據類型,使用它也能達到併發的效果
一般的思路是,在主協程取管道中的數據,這時管道會阻塞,在發送協程中向管道里塞數據,塞完數據後關閉掉管道;當主協程取不到數據,管道也關閉後,任務就完成了編程
package main import "fmt" var channel = make(chan int, 10) func main() { go func() { for i := 0; i < 10; i++ { channel <- i } close(channel) //放完數據後必定要關閉chan,不然會死鎖; }() for v := range channel { fmt.Println(v) } //在主協程從chan裏取數據,由於取不到會一直阻塞,這樣main routine就不會退出; //若是另起一個協程取數據,在另外一個協程裏阻塞,但主協程並未阻塞,取數據協程還沒取到,主協程就退出了; }
上面的例子其實並無體現出併發執行,由於十個數按次序塞進管道中,主協程按次序從管道里取出了數據,仍是一個串行的過程
package main import ( "fmt" ) var channel = make(chan int) var result = make(chan int) func main() { go func() { for i := 0; i < 100; i++ { channel <- i } close(channel) }() ct := 0 for c := range channel { go func(i int) { result <- i + i }(c) } for v := range result { ct++ fmt.Println(v) if ct == 100 { break } } }
把數據按次序投入到管道中後,遍歷管道,每取出一個數據,則開啓一個新的協程來作計算工做(i+i),而後將結果放到result隊列中,最後在主協程取出result管道的值;因爲須要關閉result管道,可是關閉的位置很差肯定,目前暫時按計算的數據量來決定跳出循環的時機,你們也可討論下何時應該關閉result管道;注意,關閉管道與監聽管道取值須要是兩個不一樣的協程,若兩個操做都在一個協程,要麼監聽了一個已經關閉的協程,要麼監聽了一個沒有被關閉的協程,都會產生異常數據結構
在這裏,管道的容量是0,即每次往管道塞數據須要等裏面的數據被消費後才能繼續塞;有容量的協程則是能夠塞進數量爲容量數的數據,以後的數據須要阻塞,直到有空餘;併發
先看代碼函數
start := time.Now() c := make(chan interface{}) ch1 := make(chan int) ch2 := make(chan int) go func() { time.Sleep(4*time.Second) close(c) }() go func() { time.Sleep(3*time.Second) ch1 <- 3 }() go func() { time.Sleep(3*time.Second) ch2 <- 5 }() fmt.Println("Blocking on read...") select { case <- c: fmt.Printf("Unblocked %v later.\n", time.Since(start)) case <- ch1: fmt.Printf("ch1 case...") case <- ch2: fmt.Printf("ch2 case...") default: fmt.Printf("default go...") }
運行上述代碼,因爲當前時間還未到3s。因此,目前程序會走default。
若註釋掉default,ch1,ch2分支會隨機挑選一個執行,若將ch1 ch2的sleep時間改成10秒,則會執行c分支;
即oop
部分參考:https://www.jianshu.com/p/2a1...this