一。並發&並行
一個應用程序 ---> 一個進程 ---> 運行在自己內存地址空間里的獨立執行體 ---> 同一個內存地址空間的一起工作的多個線程
一個並發程序 ---> 多個線程來執行任務 ---> 某個時間點同時運行在多核或者多處理器 ---> 並發&並行
---> 某個時間點同時運行在單個處理器 --\---> 並發&不並行
並行是一種通過使用多處理器以提高速度的能力。所以並發程序可以是並行的,也可以不是。
公認的,使用多線程的應用難以做到准確,最主要的問題是內存中的數據共享,它們會被多線程以無法預知的方式進行操作,導致一些無法重現或者隨機的結果(稱作 競態
)
並發方式:
1.確定性的(明確定義排序)
2.非確定性的(加鎖/互斥從而未定義排序)---> 競態
不要使用全局變量或者共享內存,它們會給你的代碼在並發運算的時候帶來危險。
解決之道:
1.同步不同的線程,對數據加鎖,這樣同時就只有一個線程可以變更數據
2.有個被稱作 Communicating Sequential Processes(順序通信處理)
(CSP, C. Hoare 發明的)
3.還有一個叫做 message passing-model(消息傳遞)
(已經運用在了其他語言中,比如 Erlang)
二。go的協程
在 Go 中,應用程序並發處理的部分被稱作 goroutines(協程)
協程 ---> 工作在相同的地址空間 ---> 共享內存的方式一定是同步的;這個可以使用 sync
包來實現(不推薦)
---> 使用 channels
來同步協程
特點:
1.使用少量的內存和資源:使用 4K 的棧內存就可以在堆中創建它們
2.對棧進行了分割,從而動態的增加(或縮減)內存的使用;棧的管理是自動的,但不是由垃圾回收器管理的,而是在協程退出后自動釋放
3.協程可以運行在多個操作系統線程之間,也可以運行在線程之內
4.使用少量的操作系統線程就能擁有任意多個提供服務的協程,而且 Go 運行時可以聰明的意識到哪些協程被阻塞了,暫時擱置它們並處理其他協程
5.存在兩種並發方式:確定性的(明確定義排序)和非確定性的(加鎖/互斥從而未定義排序)。Go 的協程和通道理所當然的支持確定性的並發方式(例如通道具有一個 sender 和一個 receiver)
實現形式:
關鍵字 go
調用 ---> 一個函數或者方法 ---> 在當前的計算過程中開始一個同時進行的函數 ---> 在相同的地址空間中並且分配了獨立的棧(棧分割)
協程的棧會根據需要進行伸縮,不出現棧溢出;開發者不需要關心棧的大小。當協程結束的時候,它會靜默退出:用來啟動這個協程的函數不會得到任何的返回值
三。go協程並行:
Go 默認沒有並行指令,只有一個獨立的核心或處理器被專門用於 Go 程序,不論它啟動了多少個協程;所以這些協程是並發運行的,但他們不是並行運行的:同一時間只有一個協程會處在運行狀態
在 gc 編譯器下(6g 或者 8g)你必須設置 GOMAXPROCS 為一個大於默認值 1 的數值來允許運行時支持使用多於 1 個的操作系統線程,所有的協程都會共享同一個線程除非將 GOMAXPROCS 設置為一個大於 1 的數。當 GOMAXPROCS 大於 1 時,會有一個線程池管理許多的線程。通過 gccgo
編譯器 GOMAXPROCS 有效的與運行中的協程數量相等。假設 n 是機器上處理器或者核心的數量。如果你設置環境變量 GOMAXPROCS>=n,或者執行 runtime.GOMAXPROCS(n)
,接下來協程會被分割(分散)到 n 個處理器上。更多的處理器並不意味着性能的線性提升。有這樣一個經驗法則,對於 n 個核心的情況設置 GOMAXPROCS 為 n-1 以獲得最佳性能,也同樣需要遵守這條規則:協程的數量 > 1 + GOMAXPROCS > 1。
所以如果在某一時間只有一個協程在執行,不要設置 GOMAXPROCS!
還有一些通過實驗觀察到的現象:在一台 1 顆 CPU 的筆記本電腦上,增加 GOMAXPROCS 到 9 會帶來性能提升。在一台 32 核的機器上,設置 GOMAXPROCS=8 會達到最好的性能,在測試環境中,更高的數值無法提升性能。如果設置一個很大的 GOMAXPROCS 只會帶來輕微的性能下降;設置 GOMAXPROCS=100,使用 top
命令和 H
選項查看到只有 7 個活動的線程。
增加 GOMAXPROCS 的數值對程序進行並發計算是有好處的;
總結:GOMAXPROCS 等同於(並發的)線程數量,在一台核心數多於1個的機器上,會盡可能有等同於核心數的線程在並行運行。