Golang 的語法和運行時直接內置了對併發的支持。
Golang 裏的併發指的是能讓某個函數獨立於其餘函數運行的能力。當一個函數建立爲 goroutine 時,Golang 會將其視爲一個獨立的工做單元。這個單元會被調度到可用的邏輯處理器上執行。Golang 運行時的調度器是一個複雜的軟件,能管理被建立的全部 goroutine 併爲其分配執行時間。這個調度器在操做系統之上,將操做系統的線程與語言運行時的邏輯處理器綁定,並在邏輯處理器上運行 goroutine。調度器在任何給定的時間,都會全面控制哪一個 goroutine 要在哪一個邏輯處理器上運行。linux
Golang 的併發同步模型來自一個叫做通訊順序進程(Communicating Sequential Processes,CSP)的範型(paradigm)。CSP 是一種消息傳遞模型,經過在 goroutine 之間傳遞數據來傳遞消息,而不是對數據進行加鎖來實現同步訪問。用於在 goroutine 之間同步和傳遞數據的關鍵數據類型叫做通道(channel)。windows
進程
當運行一個應用程序的時候,操做系統會爲這個應用程序啓動一個進程。能夠將這個進程看做一個包含了應用程序在運行中須要用到和維護的各類資源的容器。這些資源包括但不限於內存地址空間、文件和設備的句柄以及線程。網絡
線程
一個線程是一個執行空間,這個空間會被操做系統調度來運行函數中所寫的代碼。每一個進程至少包含一個線程,每一個進程的初始線程被稱做主線程。由於執行這個線程的空間是應用程序的自己的空間,因此當主線程終止時,應用程序也會終止。操做系統將線程調度到某個處理器上運行,這個處理器並不必定是進程所在的處理器。下圖展現了一個運行中的應用程序的進程和線程視圖(下圖來自互聯網):併發
本質上,不管 windows 仍是 linux,操做系統調度的單位都是線程(調度線程在 CPU 上執行)。函數
邏輯處理器
Golang 的運行時會在邏輯處理器上調度 goroutine 來運行。每一個邏輯處理器都與一個操做系統線程綁定。在 Golang 1.5 及之後的版本中,運行時默認會爲每一個可用的物理處理器分配一個邏輯處理器。spa
本地運行隊列
每一個邏輯處理器有一個本地運行隊列。若是建立一個 goroutine 並準備運行,這個 goroutine 首先會被放到調度器的全局運行隊列中。以後,調度器會將全局運行隊列中的 goroutine 分配給一個邏輯處理器,並放到這個邏輯處理器的本地運行隊列中。本地運行隊列中的 goroutine 會一直等待直到被分配的邏輯處理器執行。操作系統
下圖展現了操做系統線程、邏輯處理器和本地運行隊列之間的關係(下圖來自互聯網):線程
有時,正在運行的 goroutine 須要執行一個阻塞的系統調用,如打開一個文件。當這類調用發生時,線程和 goroutine 會從邏輯處理器上分離,該線程會繼續阻塞,等待系統調用的返回。與此同時,這個邏輯處理器就失去了用來運行的線程。因此,調度器會建立一個新線程,並將其綁定到該邏輯處理器上。以後,調度器會從本地運行隊列裏選擇另外一個 goroutine 來運行。一旦被阻塞的系統調用執行完成並返回,對應的 goroutine 會放回到本地運行隊列,而以前的線程會保存好,以便以後能夠繼續使用。看下面的圖示(下圖來自互聯網):debug
若是一個 goroutine 須要作一個網絡 I/O 調用,流程上會有些不同。在這種狀況下,goroutine 會和邏輯處理器分離,並移到集成了網絡輪詢器的運行時。一旦該輪詢器指示某個網絡讀或者寫操做已經就緒,對應的goroutine 就會從新分配到邏輯處理器上來完成操做。設計
注意:Golang 運行時默認限制每一個程序最多建立 10000 個線程。這個限制值能夠經過調用 runtime/debug 包的 SetMaxThreads 方法來更改。若是程序試圖使用更多的線程,就會崩潰。
併發(concurrency)與並行(parallelism)不一樣。並行是讓不一樣的代碼片斷同時在不一樣的物理處理器上執行。並行的關鍵是同時作不少事情,而併發是指同時管理不少事情,這些事情可能只作了一半就被暫停去作別的事情了(Golang 的併發經過切換多個線程達到減小物理處理器空閒等待的目的)。在不少狀況下,併發的效果比並行好,由於操做系統和硬件的總資源通常不多,但能支持系統同時作不少事情。這種 "使用較少的資源作更多的事情" 的哲學,也是指導 Golang 設計的哲學。
若是但願讓 goroutine 並行,必須使用多於一個邏輯處理器。當有多個邏輯處理器時,調度器會將 goroutine 平等分配到每一個邏輯處理器上。這會讓 goroutine 在不一樣的線程上運行。不過要想真的實現並行的效果,用戶須要讓本身的程序運行在有多個物理處理器的機器上。不然,哪怕 Golang 運行時使用多個線程,goroutine 依然會在同一個物理處理器上併發運行,達不到並行的效果。下圖展現了在一個邏輯處理器上併發運行 goroutine 和在兩個邏輯處理器上並行運行兩個併發的 goroutine 之間的區別(下圖來自互聯網):
參考:
《Go語言實戰》