P(執(zhí)行一個(gè)Go代碼片段所必需的資源)
P是G能夠在M中運(yùn)行的關(guān)鍵蒿赢。Go的運(yùn)行時(shí)系統(tǒng)會(huì)適時(shí)地讓P與不同的M建立或斷開關(guān)聯(lián)伴澄,以使P中的那些可運(yùn)行的G能夠及時(shí)獲得運(yùn)行時(shí)機(jī)吐绵,這與操作系統(tǒng)內(nèi)核在CPU之上實(shí)時(shí)地切換不同的進(jìn)程或線程的情形類似欲账。
改變單個(gè)Go程序間接擁有的P的最大數(shù)量有兩種方法撒汉。
- 第一種方法轿钠,調(diào)用函數(shù)runtime.GOMAXPROCS并把想要設(shè)定的數(shù)量作為參數(shù)傳入巢钓。
- 第二種方法,在Go程序運(yùn)行前設(shè)置環(huán)境變量GOMAXPROCS的值谣膳。
P的最大數(shù)量實(shí)際上是對(duì)程序中并發(fā)運(yùn)行的G的規(guī)模的一種限制竿报。P的數(shù)量即為可運(yùn)行G的隊(duì)列的數(shù)量。一個(gè)G在被啟用后继谚,會(huì)先被追加到某個(gè)P的可運(yùn)行G隊(duì)列中烈菌,以等待運(yùn)行時(shí)機(jī)。一個(gè)P只有與一個(gè)M關(guān)聯(lián)在一起時(shí),才會(huì)使其可運(yùn)行G隊(duì)列中的G有機(jī)會(huì)運(yùn)行芽世。
不過挚赊,設(shè)置P的最大數(shù)量只能限制住P的數(shù)量,而對(duì)G和M的數(shù)量沒有任何約束济瓢。當(dāng)M因系統(tǒng)調(diào)用而阻塞(更確切地說荠割,是它運(yùn)行的G進(jìn)入了系統(tǒng)調(diào)用)的時(shí)候,運(yùn)行時(shí)系統(tǒng)會(huì)把該M和與之關(guān)聯(lián)的P分離開來旺矾。這時(shí)蔑鹦,如果這個(gè)P的可運(yùn)行G隊(duì)列中還有未被運(yùn)行的G,那么運(yùn)行時(shí)系統(tǒng)就會(huì)找到一個(gè)空閑M箕宙,或創(chuàng)建一個(gè)新的M嚎朽,并與該P(yáng)關(guān)聯(lián)以滿足這些G的運(yùn)行需要。因此柬帕,M的數(shù)量在很多時(shí)候也都會(huì)比P多哟忍。而G的數(shù)量,一般取決于Go程序本身陷寝。
確定P的最大數(shù)量之后锅很,運(yùn)行時(shí)系統(tǒng)會(huì)根據(jù)這個(gè)數(shù)值重整全局的P列表(runtime. allp)。與全局M列表類似凤跑,該列表中包含了當(dāng)前運(yùn)行時(shí)系統(tǒng)創(chuàng)建的所有P爆安。運(yùn)行時(shí)系統(tǒng)會(huì)把這些P中的可運(yùn)行G全部取出,并放入調(diào)度器的可運(yùn)行G隊(duì)列中饶火。這是調(diào)整全局P列表的一個(gè)重要前提鹏控。被轉(zhuǎn)移的那些G,會(huì)在以后經(jīng)由調(diào)度再次放入某個(gè)P的可運(yùn)行G隊(duì)列肤寝。
與空閑M列表類似当辐,運(yùn)行時(shí)系統(tǒng)中也存在一個(gè)調(diào)度器的空閑P列表(runtime.sched.pidle)。當(dāng)一個(gè)P不再與任何M關(guān)聯(lián)的時(shí)候鲤看,運(yùn)行時(shí)系統(tǒng)就會(huì)把它放入該列表缘揪;而當(dāng)運(yùn)行時(shí)系統(tǒng)需要一個(gè)空閑的P關(guān)聯(lián)某個(gè)M的話,會(huì)從此列表中取出一個(gè)义桂。
注意找筝,P進(jìn)入空閑P列表的一個(gè)前提條件是它的可運(yùn)行G列表必須為空。例如慷吊,在重整全局P列表的時(shí)候袖裕,P在被清空可運(yùn)行G隊(duì)列之后,才會(huì)被放入空閑P列表溉瓶。
與M不同急鳄,P本身是有狀態(tài)的谤民,可能具有的狀態(tài)如下。
- Pidle: 此狀態(tài)表明當(dāng)前P未與任何M存在關(guān)聯(lián)疾宏。
- Prunning: 此狀態(tài)表明當(dāng)前P正在與某個(gè)M關(guān)聯(lián)张足。
- Psyscall: 此狀態(tài)表明當(dāng)前P中的運(yùn)行的那個(gè)G正在進(jìn)行系統(tǒng)調(diào)用。
- Pgcstop: 此狀態(tài)表明運(yùn)行時(shí)系統(tǒng)需要停止調(diào)度坎藐。例如为牍,運(yùn)行時(shí)系統(tǒng)在開始垃圾回收的某些步驟前,就會(huì)試圖把全局P列表中的所有P都置于此狀態(tài)岩馍。
- Pdead: 此狀態(tài)表明當(dāng)前P已經(jīng)不會(huì)再被使用碉咆。如果在Go程序運(yùn)行的過程中,通過調(diào)用runtime.GOMAXPROCS函數(shù)減少了P的最大數(shù)量兼雄,那么多余的P就會(huì)被運(yùn)行時(shí)系統(tǒng)置于此狀態(tài)吟逝。
P在創(chuàng)建之初的狀態(tài)是Pgcstop,雖然這并不意味著運(yùn)行時(shí)系統(tǒng)要在這時(shí)進(jìn)行垃圾回收赦肋。不過,P處于這一初始狀態(tài)的時(shí)間會(huì)非常短暫励稳。在緊接著的初始化之后佃乘,運(yùn)行時(shí)系統(tǒng)會(huì)將其狀態(tài)設(shè)置為Pidle,并放入調(diào)度器的空閑P列表驹尼。下圖描繪了P在各個(gè)狀態(tài)之間進(jìn)行流轉(zhuǎn)的具體情況趣避。
可以看到,非Pdead狀態(tài)的P都會(huì)在運(yùn)行時(shí)系統(tǒng)欲停止調(diào)度時(shí)被置于Pgcstop狀態(tài)新翎。不過程帕,等到需要重啟調(diào)度的時(shí)候(如垃圾回收結(jié)束后),它們并不會(huì)被恢復(fù)至原有狀態(tài)地啰,而會(huì)被統(tǒng)一地轉(zhuǎn)換為Pidle狀態(tài)愁拭。也就是說,它們會(huì)被放到同一起跑線上亏吝,并公平地接受再次調(diào)度岭埠。另一方面,非Pgcstop狀態(tài)的P都可能因全局P列表的縮小而被認(rèn)為是多余的蔚鸥,并被置于Pdead狀態(tài)惜论。
不過,我們并不用擔(dān)心其中的G會(huì)失去歸宿止喷。因?yàn)楣堇啵赑被轉(zhuǎn)換為Pdead狀態(tài)之前,其可運(yùn)行G隊(duì)列中的G都會(huì)被轉(zhuǎn)移到調(diào)度器的可運(yùn)行G隊(duì)列弹谁,而它的自由G列表中的G也都會(huì)被轉(zhuǎn)移到調(diào)度器的自由G列表中乾巧。
正如前面所述技羔,每個(gè)P中除了一個(gè)可運(yùn)行G隊(duì)列外,還都包含一個(gè)自由G列表卧抗。這個(gè)列表中包含了一些已經(jīng)運(yùn)行完成的G藤滥。隨著運(yùn)行完成的G的增多,該列表可能會(huì)很長社裆。如果它增長到一定程度拙绊,運(yùn)行時(shí)系統(tǒng)就會(huì)把其中的部分G轉(zhuǎn)移到調(diào)度器的自由G列表中。
另一方面泳秀,當(dāng)使用go語句啟用一個(gè)G的時(shí)候标沪,運(yùn)行時(shí)系統(tǒng)會(huì)先試圖從相應(yīng)P的自由G列表中獲取一個(gè)現(xiàn)成的G,來封裝這個(gè)go語句攜帶的函數(shù)(也稱go函數(shù))嗜傅,僅當(dāng)獲取不到這樣一個(gè)G的時(shí)候才有可能創(chuàng)建一個(gè)新的G金句。考慮到相應(yīng)P的自由G列表為空而獲取不到自由G的情況吕嘀,運(yùn)行時(shí)系統(tǒng)會(huì)在發(fā)現(xiàn)其中的自由G太少時(shí)违寞,預(yù)先嘗試從調(diào)度器的自由G列表中轉(zhuǎn)移過來一些G。如此一來偶房,只有在調(diào)度器的自由G列表也彈盡糧絕的時(shí)候趁曼,才會(huì)有新的G被創(chuàng)建。這在很大程度上提高了G的復(fù)用率棕洋。
在P的結(jié)構(gòu)中挡闰,可運(yùn)行G隊(duì)列和自由G列表是最重要的兩個(gè)成員。至少對(duì)于Go語言的使用者來說是這樣掰盘。它們間接地體現(xiàn)了運(yùn)行時(shí)系統(tǒng)對(duì)G的調(diào)度情況摄悯。
相關(guān)鏈接:
Go并發(fā)編程-線程模型
Go并發(fā)編程-線程模型(M)
Go并發(fā)編程-線程模型(P)
Go并發(fā)編程-線程模型(G)