細說 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
.....詳細見下面連接
微信掃碼關注站長公衆號,和站長交流學習