區(qū)別:線程中數(shù)據(jù)存儲在內(nèi)核態(tài)的內(nèi)存空間;而協(xié)程中數(shù)據(jù)存儲在線程提供的用戶態(tài)內(nèi)存空間。線程的任務(wù)調(diào)度由內(nèi)核實現(xiàn),搶占方式,依賴各種鎖;協(xié)程的任務(wù)調(diào)度由用戶態(tài)實現(xiàn)的具體調(diào)度器進行。
本教程操作環(huán)境:windows10系統(tǒng)、GO 1.11.2、thinkpad t480電腦。
協(xié)程
協(xié)程,英文名Coroutine。但在 Go 語言中,協(xié)程的英文名是:gorutine。它常常被用于進行多任務(wù),即并發(fā)作業(yè)。沒錯,就是多線程作業(yè)的那個作業(yè)。
雖然在 Go 中,我們不用直接編寫線程之類的代碼來進行并發(fā),但是 Go 的協(xié)程卻依賴于線程來進行。
下面我們來看看它們的區(qū)別。
線程的基礎(chǔ)介紹,這里請自行網(wǎng)上搜索文章,因為關(guān)于線程的優(yōu)秀介紹文章已經(jīng)很多。
協(xié)程的特點
這里先直接列出線程的特點,然后從例子中進行解析。
-
多個協(xié)程可由一個或多個線程管理,協(xié)程的調(diào)度發(fā)生在其所在的線程中。
-
可以被調(diào)度,調(diào)度策略由應(yīng)用層代碼定義,即可被高度自定義實現(xiàn)。
-
執(zhí)行效率高。
-
占用內(nèi)存少。
上面第 1和第 2點
我們來看一個例子: func TestGorutine(t *testing.T) { runtime.GOMAXPROCS(1) // 指定最大 P 為 1,從而管理協(xié)程最多的線程為 1 個 wg := sync.WaitGroup{} // 控制等待所有協(xié)程都執(zhí)行完再退出程序 wg.Add(2) // 運行一個協(xié)程 go func() { fmt.Println(1) fmt.Println(2) fmt.Println(3) wg.Done() }() // 運行第二個協(xié)程 go func() { fmt.Println(65) fmt.Println(66) // 設(shè)置個睡眠,讓該協(xié)程執(zhí)行超時而被掛起,引起超時調(diào)度 time.Sleep(time.Second) fmt.Println(67) wg.Done() }() wg.Wait()}
上面的代碼片段跑了兩個協(xié)程,運行后,觀察輸出的順序是交錯的??赡苁牵?/p>
656612367
意味著在執(zhí)行協(xié)程A的過程中,可以隨時中斷,去執(zhí)協(xié)程行B,協(xié)程B也可能在執(zhí)行過程中中斷再去執(zhí)行協(xié)程A。
看起來協(xié)程A 和 協(xié)程B 的運行像是線程的切換,但是請注意,這里的 A 和 B都運行在同一個線程里面。它們的調(diào)度不是線程的切換,而是純應(yīng)用態(tài)的協(xié)程調(diào)度。
關(guān)于上述代碼中,為什么要指定下面兩行代碼?
runtime.GOMAXPROCS(1)time.Sleep(time.Second)
這需要您去看下 Go 的協(xié)程調(diào)度入門基礎(chǔ),請看我之前的另外一篇調(diào)度分析文章:
Go 的協(xié)程調(diào)度機制
如果不設(shè)置 runtime.GOMAXPROCS(1),那么程序?qū)鶕?jù)操作系統(tǒng)的 CPU 核數(shù)而啟動對應(yīng)數(shù)量的 P,導致多個 M,即線程的啟動。那么我們程序中的協(xié)程,就會被分配到不同的線程里面去了。為了演示,故設(shè)置數(shù)量 1,使得它們都被分配到了同一個線程里面,存于線程的協(xié)程隊列里面,等待被執(zhí)行或調(diào)度。
協(xié)程特點中的第 3和第 4點。
3. 執(zhí)行效率高。
4. 占用內(nèi)存少。
因為協(xié)程的調(diào)度切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數(shù)量越多,協(xié)程的性能優(yōu)勢就越明顯。調(diào)度發(fā)生在應(yīng)用態(tài)而非內(nèi)核態(tài)。
內(nèi)存的花銷,使用其所在的線程的內(nèi)存,意味著線程的內(nèi)存可以供多個協(xié)程使用。
其次協(xié)程的調(diào)度不需要多線程的鎖機制,因為只有一個線程,也不存在同時寫變量沖突,所以執(zhí)行效率比多線程高很多。
協(xié)程和線程的整體對比
比較的點 | 線程 | 協(xié)程 |
---|---|---|
數(shù)據(jù)存儲 | 內(nèi)核態(tài)的內(nèi)存空間 | 一般是線程提供的用戶態(tài)內(nèi)存空間 |
切換操作 | 操作最終在內(nèi)核層完成,應(yīng)用層需要調(diào)用內(nèi)核層提供的 syscall 底層函數(shù) | 應(yīng)用層使用代碼進行簡單的現(xiàn)場保存和恢復即可 |
任務(wù)調(diào)度 | 由內(nèi)核實現(xiàn),搶占方式,依賴各種鎖 | 由用戶態(tài)的實現(xiàn)的具體調(diào)度器進行。例如 go 協(xié)程的調(diào)度器 |
語音支持程度 | 絕大部分編程語言 | 部分語言:Lua,Go,Python … |
實現(xiàn)規(guī)范 | 按照現(xiàn)代操作系統(tǒng)規(guī)范實現(xiàn) | 無統(tǒng)一規(guī)范。在應(yīng)用層由開發(fā)者實現(xiàn),高度自定義,比如只支持單線程的線程。不同的調(diào)度策略,等等 |
推薦學習:Golang教程