關於go併發編程的總結

關於go併發編程的總結

關於管道數據讀寫的總結

是否關閉管道 是否有緩衝 讀取次數 ?寫入次數 現象
> 讀取次數多於部分讀到的值爲管道數據結構的默認值
< 讀到多少算多少,多的值讀不了了
= 正常讀取
> 死鎖
< 不會死鎖,讀到多少算多少,多的值讀不了了
= 正常讀取
//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 empty select{}statement blocks indefinitely i.e. forever. It is similar to an empty for{}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.

併發的方法

1.使用sync進行併發:

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時,說明全部的併發函數都完成了,這時主協程就能夠退出了;我的感受這是最簡單的實現併發的方式,在須要併發處理一個集合內的全部數據時尤爲好用;

2.使用管道進行併發

管道是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,即每次往管道塞數據須要等裏面的數據被消費後才能繼續塞;有容量的協程則是能夠塞進數量爲容量數的數據,以後的數據須要阻塞,直到有空餘;併發

3. select關鍵字

先看代碼函數

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

  • 若是有一個或多個關於管道操做能夠完成,則Go運行時系統會隨機的選擇一個執行,不然的話,若是有default分支,則執行default分支語句,若是連default都沒有,則select語句會一直阻塞,直到至少有一個管道操做能夠進行.
  • 須要有真實的goroutine存在,若全部對管道操做的的子goroutine都已退出,select{}會報panic

部分參考:https://www.jianshu.com/p/2a1...this

相關文章
相關標籤/搜索