並行和並發
並發編程是指在一台處理器上“同時”處理多個任務。
宏觀並發:在一段時間內,有多個程序在同時運行。
微觀並發:在同一時刻只能有一條指令執行,但多個程序指令被快速的輪換執行,使得在宏觀上具有多個進程同時執行的效果,但在微觀上並不是同時執行的,只是把時間分成若干段,使多個程序快速交替的執行。
並行 parallel
:同一時刻,多條指令在多個處理器上同時執行。
並發 concurrency
:在同一時刻只能有一條指令執行,但多個進程指令被快速的輪換執行,使得在宏觀上具有多個進程同時執行的效果,但在微觀上並不是同時執行的,只是把時間分成若干段,通過cpu時間片輪轉使多個進程快速交替的執行。
通俗來講,並行是兩組隊列同時使用一個進程;並發是兩個隊列分別交替使用兩個進程
進程並發
程序,以Go語言為例,是指編譯好的二進制文件,在磁盤上,不占用系統資源(cpu、內存、打開的文件、設備、鎖....)
進程,是一個抽象的概念,與操作系統原理聯系緊密。以Go語言為例,將編譯好的程序運行起來,在內存空間中形成一個獨立的內存體,內存體有自己的獨立空間,上級掛靠單位是操作系統。
進程是操作系統進行資源分配和調度的一個獨立單位,一般由程序,數據集合和進程控制塊三部分組成。
- 程序:描述進程完成的功能,是控制進程執行的指令集;
- 數據集合:程序在執行時所需要的數據和工作區;
- 程序控制塊PCB:
Program Control Block
,包含進程的描述信息和控制信息,是進程存在的唯一標志。
進程是活躍的程序,占用系統資源。在內存中執行。同一個程序也可以加載為不同的進程(彼此之間互不影響)
進程狀態
進程基本的狀態有5種。分別為初始態,就緒態,運行態,掛起態與終止態。其中初始態為進程准備階段,常與就緒態結合來看。
線程的任務調度
大部分操作系統的任務調度是采用時間片輪轉的搶占式調度方式。
時間片輪轉是指,在一個進程中,當線程任務執行幾毫秒后,由操作系統內核進行調度,通過硬件計數器終端處理器,讓線程強行暫停,並將該線程的寄存器放入內存中,通過查看線程列表決定接下來執行哪一個線程,並從內存中恢復該線程的寄存器,最后恢復該線程的執行,從而去執行下一個任務。
在時間片輪轉中,任務執行那段時間叫做時間片,任務正在執行時的狀態叫運行狀態,被暫停的線程任務狀態叫做就緒狀態,意為等待下一個屬於它的時間片的到來。
由於CPU的執行效率非常高,(i5 6600 約200億/秒,奔騰4 約13億/秒)CPU preformance 時間片非常短,在各個任務之間快速地切換,給人的感覺就是多個任務在“同時進行”,這也就是我們所說的並發。多任務運行過程的示意圖如下:
進程實現並發時會出現的問題呢
孤兒進程: 父進程先於子進程結束,則子進程成為孤兒進程,子進程的父進程成為init進程,稱為init進程領養孤兒進程。
僵屍進程: 進程終止,父進程尚未回收,子進程殘留資源(PCB)存放於內核中,變成僵屍(Zombie)進程。
線程並發
在早期操作系統當中,沒有線程的概念,進程是最小分配資源與執行單位,可以看做是一個進程中只有一個線程,故進程即線程。所以線程LWP被稱為::Lightweight process
,輕量級的進程,是程序執行中一個單一的順序控制流程,在Linux操作系統下,線程的本質仍是進程。
線程有獨立的PCB,但沒有獨立的地址空間,各個線程之間共享程序的內存空間。
進程和線程的區別
- 進程:最小分配資源單位,可看成是只有一個線程的進程。
- 線程:最小的執行單位
- 一個進程由一個或多個線程組成
- 進程之間相互獨立,同一進程下的各個線程之間共享程序的內存空間
協程並發
協程 coroutines
,是一種基於線程之上,但又比線程更加輕量級的存在,這種由程序來管理的輕量級線程叫做『用戶空間線程』
,具有對內核來說不可見的特性。
多數語言在語法層面並不直接支持協程,而是通過庫的方式支持,但用庫的方式支持的功能也並不完整,比如僅僅提供協程的創建、銷毀與切換等能力。如果在這樣的輕量級線程中調用一個同步 IO 操作,比如網絡通信、本地文件讀寫,都會阻塞其他的並發執行輕量級線程,從而無法真正達到輕量級線程本身期望達到的目標。
協程和線程的區別
- 占用資源:線程,初始單位為1MB,固定不可變;協程初始一般為 2KB,可隨需要而增大。
- 調度:線程,由操作系統內核完成,協程,由用戶完成。
- 性能: 線程,占用資源高,頻繁創建銷毀帶來性能問題。占用資源小,不會帶來嚴重的性能問題。
- 數據: 線程,多線程需要鎖機制確保數據一致性和可見性;而線程因為只有一個進程,不存在同時讀/寫沖突,協程中控制共享數據不用加鎖,顧執行效率較線程高。
Go並發 goroutine
Go語言在語言級別支持協程,叫goroutine
。Go語言標准庫提供的所有系統調用操作(包括所有同步IO操作),都會出讓CPU給其他goroutine。這種輕量級線程的切換管理不依賴於系統的線程和進程,也不需要依賴於CPU的核心數量。
Go語言為並發編程而內置的上層API基於順序通信進程模型CSP(communicating sequential processes)。這就意味着顯式鎖都是可以避免的,因為Go通過相對安全的通道發送和接受數據以實現同步,這大大地簡化了並發程序的編寫。
Go語言中的並發程序主要使用兩種手段來實現。goroutine和channel。
什么是goroutine
Go語言作者Rob Pike說, “
Goroutine
是一個與其他goroutines
並發運行在同一地址空間的Go函數或方法。一個運行的程序由一個或更多個goroutine
組成。它與線程、協程、進程等不同。它是一個goroutine
*。
goroutine是Go並行設計的核心。goroutine說到底其實就是協程,它比線程更小,十幾個goroutine可能體現在底層就是五六個線程,Go語言內部幫你實現了這些goroutine之間的內存共享。執行goroutine只需極少的棧內存(大概是4~5KB),當然會根據相應的數據伸縮。也正因為如此,可同時運行成千上萬個並發任務。goroutine比thread更易用、更高效、更輕便。
MPG模型
M 操作系統的線程抽象,一個M直接關聯了一個內核線程;代表着真正執行計算的資源。
P Processor,提供相關執行環境的上下文,處理用戶級代碼邏輯的處理器,P的數量由用戶設置的GOMAXPROCS決定,但是不論GOMAXPROCS設置為多大,P的數量最大為256。
G Goroutine,G並非執行體,每個G需要綁定到P才能被調度執行。
在操作系統每一個線程都有一個固定大小的塊來做棧,這個棧會用來存儲當前正在被調用或掛起(指在調用其它函數時)的函數的內部變量。
在Go語言中,每一個goroutine是一個獨立的執行單元,goroutine的棧采取了動態擴容方式, 初始時僅為2KB,隨着任務執行按需增長,最大可達1GB(64最大1G,32位最大256M)
上圖,圖中P正在執行的Goroutine為藍色的,處於待執行狀態的Goroutine為灰色的,灰色的Goroutine形成了一個隊列runqueues。
在這里,當一個P關聯多個G時,就會處理G的執行順序,就是並發,當一個P在執行一個協程工作時,其他的會在等待,當正在執行的協程遇到阻塞情況,例如IO操作等,go的處理器就會去執行其他的協程,因為對於類似IO的操作,處理器不知道你需要多久才能執行結束,所以他不回去等你執行完。
references
go語言並發編程
進程和線程
a
groutine之間的調度