一凯旋、動態(tài)棧(Growable Stacks)
棧(stack):當(dāng)前正在被調(diào)用或被掛起(旨在調(diào)用其他函數(shù))的函數(shù)的內(nèi)部變量(local variables)被存放在棧中行剂。
棧 | 操作系統(tǒng)(OS)線程 | goroutine |
---|---|---|
類型 | 固定 | 動態(tài) |
大小 | 通常2MB | 2KB~1GB |
特征 | 若線程所需內(nèi)存較少,會造成浪費(fèi)甩苛,比如需要大量功能簡單的線程時線程數(shù)量會受到限制文判; 若需要復(fù)雜或深層的遞歸調(diào)用物赶,則可能會不夠用橡庞。 |
一般從2KB大小的棧開始生命周期,根據(jù)實際需要動態(tài)伸縮印蔗。 |
二扒最、Goroutine調(diào)度(Scheduling)
OS內(nèi)核調(diào)度
OS線程由OS內(nèi)核調(diào)度,每幾毫秒华嘹,一個硬件計時器會中斷處理器吧趣,這會調(diào)用(invoke)一個叫scheduler的內(nèi)核函數(shù)進(jìn)行線程調(diào)度
scheduler函數(shù):1. 掛起當(dāng)前執(zhí)行的線程并保存其寄存器的內(nèi)容到內(nèi)存中;2. 選擇出下一個準(zhǔn)備執(zhí)行的線程耙厚,從內(nèi)存中恢復(fù)其寄存器的數(shù)據(jù)强挫,恢復(fù)執(zhí)行該線程的現(xiàn)場并開始執(zhí)行線程。
缺點(diǎn):線程的切換需要完整的上下文切換[1](包括 a. 保存一個用戶線程的狀態(tài)到內(nèi)存薛躬;b. 恢復(fù)另一個線程的到寄存器俯渤;c. 更新調(diào)度器的數(shù)據(jù)結(jié)構(gòu))。這個操作很慢型宝,因為相關(guān)數(shù)據(jù)在內(nèi)存中的位置往往較分散(poor locality)八匠,需要大量的內(nèi)存訪問。
Go的運(yùn)行期調(diào)度(runtime scheduling)
Go runtime包含自己的scheduler趴酣,其使用一種名為m:n調(diào)度(m:n scheduling)的技術(shù)梨树。
m:n調(diào)度(m:n scheduling):在n個OS線程上多工(multiplex)[2]調(diào)度m個goroutine
Go runtime的scheduler的工作內(nèi)容與OS的scheduler是類似的,不過只關(guān)注單獨(dú)(single)Go程序中的goroutines(即某一個Go程序中的goroutine只會與該程序中的其他goroutine交換)岖寞。
何時觸發(fā)調(diào)度:不由硬件計時器觸發(fā)抡四,而是由一些Go語言的結(jié)構(gòu)(constructs)隱式地觸發(fā)。例如當(dāng)一個goroutine調(diào)用了time.Sleep()
仗谆,或者被channel或者mutex操作阻塞時指巡,調(diào)度器會使其進(jìn)入休眠并開始執(zhí)行另一個goroutine。直到Sleep()
時間結(jié)束或者channel或mutex的阻塞解除后再喚醒第一個goroutine隶垮。
關(guān)于Go的線程調(diào)度是搶占式的還是協(xié)同式的:
go在1.4版本加入了搶占式邏輯厌处,之前的版本確實是非搶占式的,1.4以后版本的rutime sysmon會定期喚醒作系統(tǒng)狀態(tài)檢查岁疼,即使P處于阻塞的系統(tǒng)調(diào)也能被調(diào)用阔涉,不至于餓死,而且還檢查某個G是否過多的占用了的cpu時間捷绒,并在某個時刻剝奪其cpu運(yùn)行時間瑰排。[3]
優(yōu)點(diǎn):這種調(diào)度方式不需要進(jìn)入內(nèi)核的上下文,所以調(diào)度一個goroutine比調(diào)度一個線程代價要低得多暖侨。
三椭住、GOMAXPROCS
GOMAXPROCS變量
- 決定會有多少個操作系統(tǒng)的線程同時執(zhí)行Go的代碼(m:n調(diào)度中的n)
- 默認(rèn)值是CPU的核心數(shù)
- 休眠中或者通信(communication)阻塞中的goroutine不需要對應(yīng)的系統(tǒng)線程
- 在被I/O或其他系統(tǒng)調(diào)用阻塞時,或調(diào)用非Go語言函數(shù)時字逗,goroutine是需要一個對應(yīng)的操作系統(tǒng)線程的京郑,不過GOMAXPROCS不需要考慮這些情況
- 修改方法:1. 修改環(huán)境變量
GOMAXPROCS=n
宅广;2. 運(yùn)行時調(diào)用runtime.GOMAXPROCS(n)
函數(shù)
最佳線程數(shù)
最佳線程數(shù)與CPU核心數(shù)的關(guān)系并沒有定論,得具體情況具體分析些举,原則是活躍線程數(shù)為 CPU(核)數(shù)時最佳[4]跟狱。
介紹CPU時有的會提到n核m線程,這里的m可以理解為CPU中單一核心支持的最大并行(parallel)線程數(shù)户魏。
描述CPU時所說的多線程:Intel的超線程(Hyper-threading)[5]技術(shù)驶臊,旨在充分利用CPU核心中的資源。簡言之叼丑,假設(shè)有兩個線程A和B关翎,若A和B都(僅)需要核心中50%的某資源進(jìn)行運(yùn)算,則在應(yīng)用了超線程的核心中鸠信,A和B兩個線程可以同時進(jìn)行運(yùn)算(微觀上的并行)纵寝。不過使用超線程技術(shù)并不一定意味著性能的提升,在一些情況下星立,性能甚至可能下降[6]店雅。
四、Goroutine沒有識別符(Identity)
識別符帶來的問題
線程的身份信息會使得做一個抽象化的thread-local storage(線程本地存儲贞铣,多線程編程中不希望其它線程訪問的內(nèi)容)變得很容易闹啦,比如一個以線程的id為key的map。而這可能會導(dǎo)致一個函數(shù)的行為不是僅由其參數(shù)辕坝,而還由其運(yùn)行在的線程所決定窍奋。
Go鼓勵更簡單的編程風(fēng)格
影響函數(shù)行為的參數(shù)(parameters)都應(yīng)被顯式地指出。這樣不僅使程序變得更易讀酱畅,而且會讓我們向一些給定的函數(shù)分配子任務(wù)時不用擔(dān)心其身份信息會影響執(zhí)行結(jié)果琳袄。
1/16/18