goroutine(1) go的調度器

對多道並行執行的程序來講,有時它要佔用處理器運行,有時要等待傳送信息,當獲得信息後又可繼續執行,一個程序的執行可能受到另外一個程序的約束。因此程序的執行其實是走走停停的,爲了能正確反映程序執行時的活動規律和狀態變化,引進了進程,以便從變化的角度,動態地分析和研究程序的執行。數據庫

用計算機系統來解決某個問題時首先必須編制程序,可把程序看做是具備獨立功能的一組指令的集合,或者說是指出處理器執行操做的步驟。程序的執行必須依賴於一個實體-數據集緩存

還有一個問題就是從程序的角度沒法正確描述程序執行時的狀態。例:只有一個「編譯程序」,同時爲多個用戶服務,有兩個須要編譯的程序甲、乙,假定編譯程序p從a點開始工做,如今正在編譯程序甲,當工做到b點時,須要把中間結果記錄在磁盤上。因而程序p在b 點等待磁盤傳輸信息。這時處理器空閒,這時讓p爲源程序 乙進行編譯,編譯程序仍從a點開始工做。如今 的問題是如何來描述p的狀態?是說「它在b點等侍磁盤傳輸」仍是說「從a點開始執行?多線程

1.進程和線程併發

進程:把一個程序在一個數據集上的一次執行稱爲一個進程spa

  (程序是靜止的,進程是動態的。在20世紀80年代,大多數操做系統仍採用進程技術,把進程做爲操做系統的基本組成單位。進程既是資源分配單位,又是調度和執行單位)操作系統

線程:線程是進程中可獨立執行的子任務。線程

  (把一個計算問題或一個應用問題做爲一個進程,把該進程中能夠併發執行的各部分分別做爲線程。一個數據庫應用程序的運行建立了一個數據庫進程,用戶要求從數據庫中生產一份工資報表,並將它傳送到一個文件中。用戶在等待報表生成的時候又向數據庫提出一個查詢請求。操做系統把用戶的每個請求(工資單報表,數據庫查詢)分別做爲數據庫進程的一個線程)協程

爲何引入線程?blog

  1.每一個進程都要佔用一個進程控制塊和一個私有的主存空間,開銷比較大;進程之間傳遞消息時要從一個工做區傳到另外一個工做區,需專用通訊機制,速度較慢隊列

  2.進程增多就增長了進程調度的次數,給調度和控制帶來了複雜性。

  在採用線程的操做系統中,進程是資源分配 單位,而線程是調度、執行單位。

協程:獨⽴的棧空間,共享堆空間,調度由⽤戶⾃⼰控制,本質上有點相似於⽤戶級線程,這些⽤戶級線程的調度也是⾃⼰實現的 。⼀個線程上能夠跑多個協程,協程是輕量級的線程。

進程——>一個線程——>單線程程序

進程——>多線程——>多線程程序

2.併發和並行

1)多線程程序在cpu一個核蕊上運行就是併發

2)多線程程序在cpu多個核蕊上運行就是並行

併發是邏輯上的同時發生(simultaneous),而並行是物理上的同時發生。

併發:

 

並行:

 3.用戶空間線程和內核空間線程之間的映射關係 

N:1,   1:1和   M:N

N:1是說,多個(N)用戶線程始終在一個內核線程上跑,context上下文切換確實很快,可是沒法真正的利用多核。

1:1是說,一個用戶線程就只在一個內核線程上跑,這時能夠利用多核,可是上下文switch很慢。

M:N是說, 多個goroutine在多個內核線程上跑,這個看似能夠集齊上面二者的優點,可是無疑增長了調度的難度。

4.go的調度器

go 的高度器的內部結構:M P G

 

 

M:表明真正的內核OS線程,和POSIX裏的thread差很少,真正幹活的人
G:表明一個goroutine,它有本身的棧,instruction pointer和其餘信息(正在等待的channel等等),用於調度。
P:表明調度的上下文,能夠把它看作一個局部的調度器,使go代碼在一個線程上跑,它是實現從N:1到N:M映射的關鍵。
 

圖中看,有2個物理線程M,每個M都擁有一個context(P),每個也都有一個正在運行的goroutine。

P的數量能夠經過GOMAXPROCS()來設置,它其實也就表明了真正的併發度,即有多少個goroutine能夠同時運行。

圖中灰色的那些goroutine並無運行,而是出於ready的就緒態,正在等待被調度。P維護着這個隊列(稱之爲runqueue),

Go語言裏,啓動一個goroutine很容易:go function 就行,因此每有一個go語句被執行,runqueue隊列就在其末尾加入一個
goroutine,在下一個調度點,就從runqueue中取出(如何決定取哪一個goroutine?)一個goroutine執行。
 
爲什麼要維護多個上下文P?由於當一個OS線程被阻塞時,P能夠轉而投奔另外一個OS線程!
圖中看到,當一個OS線程M0陷入阻塞時,P轉而在OS線程M1上運行。調度器保證有足夠的線程來運行因此的context P。
圖中的M1多是被建立,或者從線程緩存中取出。

 

當MO返回時,它必須嘗試取得一個context P來運行goroutine,通常狀況下,它會從其餘的OS線程那裏steal偷一個context過來,
若是沒有偷到的話,它就把goroutine放在一個global runqueue裏,而後本身就去睡大覺了(放入線程緩存裏)。Contexts們也會週期性的檢查global runqueue,不然global runqueue上的goroutine永遠沒法執行。

 

另外一種狀況是P所分配的任務G很快就執行完了(分配不均),這就致使了一個上下文P閒着沒事兒幹而系統卻任然忙碌。可是若是global runqueue沒有任務G了,那麼P就不得不從其餘的上下文P那裏拿一些G來執行。通常來講,若是上下文P從其餘的上下文P那裏要偷一個任務的話,通常就‘偷’run queue的一半,這就確保了每一個OS線程都能充分的使用。
參考:https://www.zhihu.com/question/20862617
相關文章
相關標籤/搜索