Golang 入門 : 理解併發與並行

Golang 的語法和運行時直接內置了對併發的支持。
Golang 裏的併發指的是能讓某個函數獨立於其他函數運行的能力。當一個函數創建爲 goroutine 時,Golang 會將其視爲一個獨立的工作單元。這個單元會被調度到可用的邏輯處理器上執行。Golang 運行時的調度器是一個複雜的軟件,能管理被創建的所有 goroutine 併爲其分配執行時間。這個調度器在操作系統之上,將操作系統的線程與語言運行時的邏輯處理器綁定,並在邏輯處理器上運行 goroutine。調度器在任何給定的時間,都會全面控制哪個 goroutine 要在哪個邏輯處理器上運行。

Golang 的併發同步模型來自一個叫作通信順序進程(Communicating Sequential Processes,CSP)的範型(paradigm)。CSP 是一種消息傳遞模型,通過在 goroutine 之間傳遞數據來傳遞消息,而不是對數據進行加鎖來實現同步訪問。用於在 goroutine 之間同步和傳遞數據的關鍵數據類型叫作通道(channel)。

進程與線程

進程

當運行一個應用程序的時候,操作系統會爲這個應用程序啓動一個進程。可以將這個進程看作一個包含了應用程序在運行中需要用到和維護的各種資源的容器。這些資源包括但不限於內存地址空間、文件和設備的句柄以及線程。

線程

一個線程是一個執行空間,這個空間會被操作系統調度來運行函數中所寫的代碼。每個進程至少包含一個線程,每個進程的初始線程被稱作主線程。因爲執行這個線程的空間是應用程序的本身的空間,所以當主線程終止時,應用程序也會終止。操作系統將線程調度到某個處理器上運行,這個處理器並不一定是進程所在的處理器。下圖展示了一個運行中的應用程序的進程和線程視圖(下圖來自互聯網):

Golang 入門 : 理解併發與並行

本質上,無論 windows 還是 linux,操作系統調度的單位都是線程(調度線程在 CPU 上執行)。

邏輯處理器與本地運行隊列

邏輯處理器

Golang 的運行時會在邏輯處理器上調度 goroutine 來運行。每個邏輯處理器都與一個操作系統線程綁定。在 Golang 1.5 及以後的版本中,運行時默認會爲每個可用的物理處理器分配一個邏輯處理器。

本地運行隊列

每個邏輯處理器有一個本地運行隊列。如果創建一個 goroutine 並準備運行,這個 goroutine 首先會被放到調度器的全局運行隊列中。之後,調度器會將全局運行隊列中的 goroutine 分配給一個邏輯處理器,並放到這個邏輯處理器的本地運行隊列中。本地運行隊列中的 goroutine 會一直等待直到被分配的邏輯處理器執行。

下圖展示了操作系統線程、邏輯處理器和本地運行隊列之間的關係(下圖來自互聯網):

Golang 入門 : 理解併發與並行

有時,正在運行的 goroutine 需要執行一個阻塞的系統調用,如打開一個文件。當這類調用發生時,線程和 goroutine 會從邏輯處理器上分離,該線程會繼續阻塞,等待系統調用的返回。與此同時,這個邏輯處理器就失去了用來運行的線程。所以,調度器會創建一個新線程,並將其綁定到該邏輯處理器上。之後,調度器會從本地運行隊列裏選擇另一個 goroutine 來運行。一旦被阻塞的系統調用執行完成並返回,對應的 goroutine 會放回到本地運行隊列,而之前的線程會保存好,以便之後可以繼續使用。看下面的圖示(下圖來自互聯網):

Golang 入門 : 理解併發與並行

如果一個 goroutine 需要做一個網絡 I/O 調用,流程上會有些不一樣。在這種情況下,goroutine 會和邏輯處理器分離,並移到集成了網絡輪詢器的運行時。一旦該輪詢器指示某個網絡讀或者寫操作已經就緒,對應的goroutine 就會重新分配到邏輯處理器上來完成操作。

注意:Golang 運行時默認限制每個程序最多創建 10000 個線程。這個限制值可以通過調用 runtime/debug 包的 SetMaxThreads 方法來更改。如果程序試圖使用更多的線程,就會崩潰。

併發與並行

併發(concurrency)與並行(parallelism)不同。並行是讓不同的代碼片段同時在不同的物理處理器上執行。並行的關鍵是同時做很多事情,而併發是指同時管理很多事情,這些事情可能只做了一半就被暫停去做別的事情了(Golang 的併發通過切換多個線程達到減少物理處理器空閒等待的目的)。在很多情況下,併發的效果比並行好,因爲操作系統和硬件的總資源一般很少,但能支持系統同時做很多事情。這種 "使用較少的資源做更多的事情" 的哲學,也是指導 Golang 設計的哲學。

如果希望讓 goroutine 並行,必須使用多於一個邏輯處理器。當有多個邏輯處理器時,調度器會將 goroutine 平等分配到每個邏輯處理器上。這會讓 goroutine 在不同的線程上運行。不過要想真的實現並行的效果,用戶需要讓自己的程序運行在有多個物理處理器的機器上。否則,哪怕 Golang 運行時使用多個線程,goroutine 依然會在同一個物理處理器上併發運行,達不到並行的效果。下圖展示了在一個邏輯處理器上併發運行 goroutine 和在兩個邏輯處理器上並行運行兩個併發的 goroutine 之間的區別(下圖來自互聯網):
Golang 入門 : 理解併發與並行

參考:

《Go語言實戰》

作者:sparkdev
出處:http://www.cnblogs.com/sparkdev/

51Reboot golang課程 6.15開班

有想要諮詢的WeChat:17812796384

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章