1. 程式人生 > >Golang:執行緒 和 協程 的區別

Golang:執行緒 和 協程 的區別

作者:林冠巨集 / 指尖下的幽靈

部落格:http://www.cnblogs.com/linguanh/

GitHub : https://github.com/af913337456/

掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8

騰訊雲專欄: https://cloud.tencent.com/developer/user/1148436/activities


目錄

  • 前言
  • 協程
  • 協程的特點
    • 第 1第 2
    • 特點中的第 3 和 第 4 點
  • 和執行緒的整體對比

前言

國慶越快各位,距離上次發文快兩個月了,19年也快結束了。現在的總結更多是放在了草稿

而沒有發出,這次詳細分享下在 Go 中,執行緒和協程的區別及其關係

協程

協程,英文名Coroutine。但在 Go 語言中,協程的英文名是:gorutine。它常常被用於進行多工,即併發作業。沒錯,就是多執行緒作業的那個作業。

雖然在 Go 中,我們不用直接編寫執行緒之類的程式碼來進行併發,但是 Go 的協程卻依賴於執行緒來進行。

下面我們來看看它們的區別。

執行緒的基礎介紹,這裡請自行網上搜索文章,因為關於執行緒的優秀介紹文章已經很多。

協程的特點

這裡先直接列出執行緒的特點,然後從例子中進行解析。

  1. 多個協程可由一個或多個執行緒管理,協程的排程發生在其所在的執行緒中。
  2. 可以被排程,排程策略由應用層程式碼定義,即可被高度自定義實現。
  3. 執行效率高。
  4. 佔用記憶體少。

上面第 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 的協程排程入門基礎,請看我之前的另外一篇排程分析文章:

Go 的協程排程機制

如果不設定 runtime.GOMAXPROCS(1),那麼程式將會根據作業系統的 CPU 核數而啟動對應數量的 P,導致多個 M,即執行緒的啟動。那麼我們程式中的協程,就會被分配到不同的執行緒裡面去了。為了演示,故設定數量 1,使得它們都被分配到了同一個執行緒裡面,存於執行緒的協程佇列裡面,等待被執行或排程。

協程特點中的第 3 和 第 4 點。

  1. 執行效率高。
  2. 佔用記憶體少。

因為協程的排程切換不是執行緒切換,而是由程式自身控制,因此,沒有執行緒切換的開銷,和多執行緒比,執行緒數量越多,協程的效能優勢就越明顯。排程發生在應用態而非核心態。

記憶體的花銷,使用其所在的執行緒的記憶體,意味著執行緒的記憶體可以供多個協程使用。

其次協程的排程不需要多執行緒的鎖機制,因為只有一個執行緒,也不存在同時寫變數衝突,所以執行效率比多執行緒高很多。

和執行緒的整體對比

<
比較的點 執行緒 協程
資料儲存 核心態的記憶體空間 一般是執行緒提供的使用者態記憶體空間
切換操作 操作最終在核心層完成,應用層需要呼叫核心層提供的 syscall 底層函式 應用層使用程式碼進行簡單的現場儲存和恢復即可
任務排程 由核心實現,搶佔方式,依賴各種鎖 由使用者態的實現的具體排程器進行。例如 go 協程的排程器
語音支援程度 絕大部分程式語言 部分語言:Lua,Go,Python ...
實現規範 按照現代作業系統規範實現 無統一規範。在應用層由開發者實現,高度自定義,比如只支援單執行緒的執行緒。不同的排程策略,等等