做者:林冠宏 / 指尖下的幽靈git
掘金:juejin.im/user/587f0d…github
GitHub : github.com/af913337456…多線程
騰訊雲專欄: cloud.tencent.com/developer/u…併發
第 1
和 第 2
點國慶愉快各位,距離上次發文快兩個月了,19年也快結束了。如今的總結更可能是放在了草稿
而沒有發出,此次詳細分享下在 Go 中,線程和協程的區別及其關係
。編程語言
協程,英文名Coroutine
。但在 Go 語言中,協程的英文名是:gorutine
。它經常被用於進行多任務
,即併發做業
。沒錯,就是多線程
做業的那個做業。函數
雖然在 Go 中,咱們不用直接編寫線程之類的代碼來進行併發,可是 Go 的協程卻依賴於線程
來進行。post
下面咱們來看看它們的區別。性能
線程的基礎介紹,這裏請自行網上搜索文章,由於關於線程的優秀介紹文章已經不少。區塊鏈
這裏先直接列出線程的特色,而後從例子中進行解析。
協程的調度
發生在其所在的線程中。第 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,使得它們都被分配到了同一個線程裏面,存於線程的協程隊列裏面,等待被執行或調度。
由於協程的調度切換不是線程切換
,而是由程序自身控制,所以,沒有線程切換的開銷
,和多線程比,線程數量越多,協程的性能優點就越明顯。調度發生在應用態而非內核態。
內存的花銷,使用其所在的線程的內存,意味着線程的內存能夠供多個協程使用。
其次協程的調度不須要多線程的鎖機制
,由於只有一個線程,也不存在同時寫變量衝突
,因此執行效率比多線程高不少。
比較的點 | 線程 | 協程 |
---|---|---|
數據存儲 | 內核態的內存空間 | 通常是線程提供的用戶態內存空間 |
切換操做 | 操做最終在內核層完成,應用層須要調用內核層提供的 syscall 底層函數 | 應用層使用代碼進行簡單的現場保存和恢復便可 |
任務調度 | 由內核實現,搶佔方式,依賴各類鎖 | 由用戶態的實現的具體調度器進行。例如 go 協程的調度器 |
語音支持程度 | 絕大部分編程語言 | 部分語言:Lua,Go,Python ... |
實現規範 | 按照現代操做系統規範實現 | 無統一規範。在應用層由開發者實現,高度自定義,好比只支持單線程的線程。不一樣的調度策略,等等 |
本人技術書籍《區塊鏈以太坊DApp開發實戰》現已出版並可網購了,適合初中級區塊鏈技術相關研發人員閱讀。