8.1 并發的含義
- 并發:邏輯上具備同時處理多個任務的能力
- 并行: 物理上在同一時刻執行多個并發任務
多線程或多進程是并行的基本條件,但單線程也可用協程做到并發,在單個線程上通過主動切換來實現多任務并發。
goroutine不是簡單的協程,更像是多線程和協程的結合體。
go并非執行并發操作,而是創建一個并發任務單元,等待調度器安排合適的系統線程去執行。
當前流程不阻塞,不等待,也不保證并發順序!
- 每個任務單元,保存函數指針,調用參數,還會分配棧內存空間,go自定義棧初始僅須2KB,需要時擴容
- 類似defer,因“延遲執行”而立即計算并復制執行參數
Wait
- 進程退出時不會等待并發任務結束,可用channel阻塞,然后發出退出信號
- 若等待多個結束,推薦使用sync.WaitGroup,計數器直至歸零,在goroutine外進行WaitGroup.Add
- 可在多處使用wait阻塞,都能接收到通知
GOMAXPROCS
僅有限的幾個線程參與并發任務執行,與處理器核數相等,用runtime.GOMAXPROCS函數修改
Local Storage
goroutine:
- 無優先級
- 無編號
- 無局部存儲
- 拋棄返回值
實際上除了優先級,其它功能都容易實現。
Gosched
暫停,釋放線程去執行其它任務,當前任務放回隊列,等待下次調度。
Goexit
立即終止當前任務,確保所有已注冊延遲調用被執行。
8.2 通道
Go鼓勵使用CSP(communicating Sequential Process)通道,以通信來代替內存共享,實現并發安全。
- 底層來講,通道是個隊列
- 同步模式:發送和接受雙方配對,然后直接復制數據給對方。若配對失敗,則置入等待隊列,直到喚醒。
- 異步模式:搶奪數據緩沖槽
- 可以用ok-idom 或 range 模式處理數據
- 通知可以是全體性的,比如close,一次性事件用close很好
對于close和nil通道,規則如下:
- 向已關閉通道發送數據,引發panic
- 從已關閉接收數據,返回已緩沖數據或零值
- 無論收發,nil通道都會阻塞
單向
通道默認雙向,不區分發送和接收端。
通常使用類型轉換來獲取單向通道,并分別賦予操作雙方
c := make(chan int)
var send chan<- int = c
var recv <-chan int = c
- 不能在單向通道上做逆向操作
- close不能用于接收端,這說明close是發送一個信號!
close(ready) = close<-
- 無法將單向通道轉換回去
選擇
- select語句,隨機選擇一個可用通道做收發操作
- 如要等全部通道消息處理結束,可將已完成通道設置為nil,這樣就阻塞,不會被select再選中
- 即便同一通道,也隨機選擇case執行
- 所有通道都不可用時,select執行default語句
模式
通常使用工廠方法將goroutinue和通道綁定
type receiver struct {
sync.WaitGroup
data chan int
}
性能
將發往通道的數據打包,減少傳輸次數,可有效提升性能,比如傳數組。
畢竟實現上,通道隊列依舊是鎖同步機制。
資源泄露
goroutine leak:即goroutine處于發送或接受阻塞狀態,但一直未被喚醒,垃圾回收器也不管,導致在等待隊列里長久休眠。
8.3 同步
通道作為一種通信,也不是就取代鎖了。
- 通道傾向于解決邏輯層次的并發處理
- 鎖則用來保護局部范圍內的數據安全
要注意,Mutex作為匿名字段時,相關方法要實現為指針receiver,否則復制導致鎖失效
- 控制鎖的粒度
- 不支持遞歸鎖
- 對性能要求高時,應避免defer Unlock
- 讀寫并發,使用RWmutex
- 對單個數據讀寫保護,使用原子操作