Golang:執行緒 和 協程 的區別
作者:林冠巨集 / 指尖下的幽靈
GitHub : github.com/af913337456…
目錄
- 前言
- 協程
- 協程的特點
-
第 1
和第 2
點 - 特點中的第 3 和 第 4 點
-
- 和執行緒的整體對比
前言
國慶愉快各位,距離上次發文快兩個月了,19年也快結束了。現在的總結更多是放在了草稿
而沒有發出,這次詳細分享下在 Go 中,執行緒和協程的區別及其關係
。
協程
協程,英文名Coroutine
gorutine
。它常常被用於進行多工
,即併發作業
。沒錯,就是多執行緒
作業的那個作業。
雖然在 Go 中,我們不用直接編寫執行緒之類的程式碼來進行併發,但是 Go 的協程卻依賴於執行緒
來進行。
下面我們來看看它們的區別。
執行緒的基礎介紹,這裡請自行網上搜索文章,因為關於執行緒的優秀介紹文章已經很多。
協程的特點
這裡先直接列出執行緒的特點,然後從例子中進行解析。
- 多個協程可由一個或多個執行緒管理,
協程的排程
發生在其所在的執行緒中。 - 可以被排程,排程策略由應用層程式碼定義,即可被高度自定義實現。
- 執行效率高。
- 佔用記憶體少。
上面第 1
和 第 2
點
我們來看一個例子:
func TestGorutine(t *testing.T) {
runtime.GOMAXPROCS(1) // 指定最大 P 為 1,從而管理協程最多的執行緒為 1 個
wg := sync.WaitGroup{} // 控制等待所有協程都執行完再退出程式
wg.Add(2)
// 執行一個協程
go func() {
fmt.Println(1)
fmt.Println(2)
fmt.Println(3)
wg.Done()
}()
// 執行第二個協程
go func() {
fmt.Println(65)
fmt.Println(66 )
// 設定個睡眠,讓該協程執行超時而被掛起,引起超時排程
time.Sleep(time.Second)
fmt.Println(67)
wg.Done()
}()
wg.Wait()
}
複製程式碼
上面的程式碼片段跑了兩個協程,執行後,觀察輸出的順序是交錯
的。可能是:
65
66
1
2
3
67
複製程式碼
意味著在執行協程A的過程中,可以隨時中斷
,去執協程行B,協程B也可能在執行過程中中斷再去執行協程A。
看起來協程A 和 協程B 的執行像是執行緒的切換,但是請注意,這裡的 A 和 B 都執行在同一個執行緒裡面。它們的排程不是執行緒的切換,而是純應用態的協程排程
。
關於上述程式碼中,為什麼要指定下面兩行程式碼?
runtime.GOMAXPROCS(1)
time.Sleep(time.Second)
複製程式碼
這需要您去看下 Go 的協程排程入門基礎,請看我之前的另外一篇排程分析文章:
如果不設定 runtime.GOMAXPROCS(1)
,那麼程式將會根據作業系統的 CPU 核數而啟動對應數量的 P,導致多個 M,即執行緒的啟動。那麼我們程式中的協程,就會被分配到不同的執行緒
裡面去了。為了演示,故設定數量 1,使得它們都被分配到了同一個執行緒裡面,存於執行緒的協程佇列裡面,等待被執行或排程。
協程特點中的第 3 和 第 4 點。
- 執行效率高。
- 佔用記憶體少。
因為協程的排程切換不是執行緒切換
,而是由程式自身控制,因此,沒有執行緒切換的開銷
,和多執行緒比,執行緒數量越多,協程的效能優勢就越明顯。排程發生在應用態而非核心態。
記憶體的花銷,使用其所在的執行緒的記憶體,意味著執行緒的記憶體可以供多個協程使用。
其次協程的排程不需要多執行緒的鎖機制
,因為只有一個執行緒,也不存在同時寫變數衝突
,所以執行效率比多執行緒高很多。
和執行緒的整體對比
比較的點 | 執行緒 | 協程 |
---|---|---|
資料儲存 | 核心態的記憶體空間 | 一般是執行緒提供的使用者態記憶體空間 |
切換操作 | 操作最終在核心層完成,應用層需要呼叫核心層提供的 syscall 底層函式 | 應用層使用程式碼進行簡單的現場儲存和恢復即可 |
任務排程 | 由核心實現,搶佔方式,依賴各種鎖 | 由使用者態的實現的具體排程器進行。例如 go 協程的排程器 |
語音支援程度 | 絕大部分程式語言 | 部分語言:Lua,Go,Python ... |
實現規範 | 按照現代作業系統規範實現 | 無統一規範。在應用層由開發者實現,高度自定義,比如只支援單執行緒的執行緒。不同的排程策略,等等 |
個人廣告
本人技術書籍《區塊鏈以太坊DApp開發實戰》現已出版並可網購了,適合初中級區塊鏈技術相關研發人員閱讀。