細說 goroutine 和 channel

+  goroutine 看一個需求

 * 需求:要求統計 1 - 90000000000 的數字中,那些是素數?

+  分析思路:

 * 傳統的方法,就是使用一個循環,循環的判斷各個數是否是素數「效率很低」
 * 使用併發或者並行的方式,將統計素數的任務分配個多個goroutine去完成,這時就會使用到goroutine「速度提升不少」


 + goroutine 基本介紹
 
 > 進程和線程介紹
 
  * 進程就是程序程序在操做系統中的一次執行過程,是系統進行資源分配和調度的基本單元
  * 線程是進程的一個執行實例,是程序執行的最小單元,它是比進程更小的能獨立運行的基本單元
  * 一個進程能夠建立和銷燬多個線程,同一個進程中的多個線程能夠併發執行
  * 一個程序至少有一個進程,一個進程至少有一個線程

 + 併發和並行

  * 多線程程序在單核上運行,就是併發
  * 多線程程序在多核上運行,就是並行

 + 小結

  * 併發: 由於是在一個cpu上, 好比有10個線程,每一個線程執行10ms(進行輪詢操做),從人的角度看,好像這10個線程都在運行,可是從微觀上看,
  在某個時間點看,其實只有一個線程在執行,這就是併發。

  * 並行: 由於是在多個cpu上(好比有10個cpu),好比有10個線程,每一個線程執行10ms(各自在不一樣的cpu上執行),從人的角度看,這10個線程都在運行,可是從微觀上看,在某個時間點看,也同時有10個線程在執行,這就是並行

 + go 協程和go主線程

  * go主線程(有程序員直接稱爲線程/也能夠理解程進程) : 一個go線程上,能夠起多個協程,你能夠這裏理解,協程是輕量級的線程「編譯器作優化」
 
 + go 協程的特色

  * 有獨立的棧空間
  * 共享程序堆空間
  * 調度由用戶控制
  * 協程是輕量級的線程

+ channel (管道)-看個需求
 
  * 需求: 如今要計算1-200 的各個數的階乘,而且把各個數的階乘放入到map中,最後顯示出來,要求使用goroutine完成

> 分析思路

  * 使用goroutine來完成,效率高,可是會出現併發/並行安全問題
  * 這裏就提出了不一樣的goroutine如何通訊的問題

> 代碼實現

  * 使用goroutine 來完成(看看使用goroutine併發完成會出現什麼問題? 而後咱們去解決)
  * 在運行某個程序時,如何知道是否存在資源競爭問題,方法很簡單,在編譯程序時。增長一個參數。 -race便可
  * 代碼實現見  goroutine 和 channel/goroutine_channel.go

  > 上面的案例就出現了資源的競爭的問題

+ 不一樣goroutine之間如何通信

  * 全局變量的互斥鎖
  * 使用管道channel來解決

+ 使用全局變量加鎖同步改進程序

  * 由於沒有對全局變量m加鎖,所以會出現資源爭奪問題,代碼會出現錯誤,提示 fatal error: concurrent map writes
  * 解決方案: 加入互斥鎖
  * 咱們的數的階乘很大,結果會越界,能夠將求階乘改爲sum+=uint64(i)
  * 改進代碼實現見  goroutine 和 channel/goroutine_channel_new.go

+ 爲何須要channel

  * 前面使用全局變量加鎖同步來解決goroutine 的通信,但不完美
  * 主線程在等待全部goroutine所有完成的時間很難肯定,咱們這裏設置10s,僅僅是估算
  * 若是主線程休眠時間長了,會加長等待時間,若是時間短了,可能還有goroutine處於工做狀態,這時也會隨主線程的退出而銷燬
  * 同步全局變量加鎖同步來通信,也不利用多個協程對全局變量的讀寫操做
  * 上面種種分析都在呼喚一個新的通信機制- channel
 
+ channel 的基本介紹
 
  * channel本質就是一個數據結構-隊列
  * 數據是先進先出(FIFO: first in first out)
  * 線程安全,多goroutine訪問時,不須要加鎖,就是說channel自己就是線程安全的
  * channel有類型的,一個string的channel只能存放string類型數據
  * channel 是線程安全的,多個協程操做同一個管道時,不會發生資源競爭問題

+ 定義/聲明channel

  * var 變量名 chan 數據類型
  * 舉例:
      var intChan chan int (intChan 用戶存放int數據)
      var mapChan chan map[int]string (mapChan 用戶存放map[int]string類型)
      var perChan chan Person
      var PerChan2 chan *Person
      ....
   > 說明
    - channel 是引用類型
    - channel必須初始化才能寫入數據,即make後才能使用
    - 管道是有類型的,intChan只能寫入整型int   

+ 管道的初始化,寫入數據到管道,從管道讀取數據以及基本注意事項

  * 代碼實現見  goroutine 和 channel/channel.go

+ channel 使用的注意事項

  * channel 中只能存放指定的數據類型
  * channel 的數據放滿後,就不能再放入了
  * 若是從channel取出數據後,能夠繼續放入
  * 在沒有使用協程的狀況下,若是channel數據取完了,在取,就會報dead lock

+ 讀寫channel 案例演示

  * 案例見 goroutine 和 channel/intChan.go     
  * 案例見 goroutine 和 channel/mapChan.go 
  * 案例見 goroutine 和 channel/catChan.go 
  * 案例見 goroutine 和 channel/catChan2.go 

- channel 的遍歷和關閉

+ channel的關閉
  
  * 使用內置函數close 能夠關閉channel,當channel關閉後,就不能再向channel寫數據了,可是仍然能夠從該channel讀取數據
  * 案例演示見goroutine 和 channel/close.go

- channel 的遍歷

+ channel支持for-range的方式進行遍歷,請注意兩個細節

  * 再遍歷時,若是channel沒有關閉,則會出現deadlock的錯誤。
  * 再遍歷時。若是channel已經關閉,則會正常遍歷數據,遍歷完後,就會退出遍歷

- channel遍歷和關閉的案例演示

+ 代碼演示 goroutine 和 channel/channel_for_close.go

- 應用實例

+ 請完成gorouteine 和 channel協同工做的案例。具體要求
  
  * 開啓一個writeData協同,向管道intChan中寫入50個整數
  * 開啓一個readData協程,從管道intChan中讀取writeData寫入的數據
  * 注意: writeData 和 readData操做的是同一個管道
  * 主線程須要等待writeData 和 readData協程都完成工做才能退出[管道]
  * 案例代碼  goroutine 和 channel/gorouteine.go


+ channel 使用細節和注意事項
  
  * channel能夠聲明爲只讀,或者只協性質
  案例 chan01.go
  * channel 只讀和只寫的最佳實踐案例
   案例 chan02.go
  * 使用select能夠解決從管道取數據的阻塞問題
  * goroutine 中使用recover,解決協程中出現panic,致使程序崩潰問題
  * 若是咱們起一個協程,可是這個協程出現了panic,若是咱們沒有捕獲這個panic,就會形成整個程序崩潰,這時咱們
  能夠再goroutine中使用recover來捕獲panic,進行處理,這樣即便這個協程發生的問題,可是主線程仍然不受影響,能夠
  繼續執行
.....詳細見下面鏈接

細說 goroutine 和 channelgit

相關文章
相關標籤/搜索