本篇內容介紹了“Go語言中如何對棧進行處理”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
網站建設哪家好,找創(chuàng)新互聯(lián)!專注于網頁設計、網站建設、微信開發(fā)、微信小程序定制開發(fā)、集團企業(yè)網站建設等服務項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了樅陽免費建站歡迎大家使用!
一、線程棧(thread stacks)介紹
在我們研究Go的棧處理方式之前,我們先來看看傳統(tǒng)語言,比如C是如何進行棧管理的。
當你啟動一個C實現(xiàn)的thread時,C標準庫會負責分配一塊內存作為這個線程的棧。標準庫分配這塊內存,告訴內核它的位置并讓內核處理這個線程 的執(zhí)行。不過當這塊內存不夠用時,問題就來了,我們來看一下下面這個函數(shù):
int a(int m, int n) { if (m == 0) { return n + 1; } else if (m > 0 && n == 0) { return a(m – 1, 1); } else { return a(m – 1, a(m, n – 1)); } }
這個函數(shù)大量使用遞歸,執(zhí)行a(4, 5)就會降所有棧內存耗盡。要解決這個問題,你可以調整標準庫給線程棧分配的內存塊的大小。但是全線提高棧大小意味著每個線程都會提高棧的內存使用量,即 便它們不是大量采用遞歸方式的。這樣一來,你將用光所有內存,即便你的程序還尚未使用棧上的內存。
另外一種可選的解決方法則是為每個線程單獨確定棧大小。這樣一來你就不得不完成這樣的任務:根據(jù)每個線程的需要,估算它們的棧內存的大小。這將是 創(chuàng)建線程的難度超出我們的期望。想搞清楚一般情況下一個線程棧需要多少內存是不可行的,即便是通常情況也是非常困難的。
二、Go是如何應對這個問題的
Go運行時會試圖按需為goroutine提供它們所需要的棧空間,而不是為每個goroutine分配一個固定大小的??臻g。這樣可以把程序員 們從決定??臻g大小的煩心事中解脫了出來。不過Go核心團隊正在嘗試切換到另外一種方案,這里我將嘗試闡述舊方案以及它的缺點,新方案以及為何要 做出如此改變。
三、分段棧(Segmented Stacks)
分段棧(segmented stacks)是Go語言最初用來處理棧的方案。當創(chuàng)建一個goroutine時,Go運行時會分配一段8K字節(jié)的內存用于棧供goroutine運行使 用,我們讓goroutine在這個棧上完成其任務處理。
當我們用光這8K字節(jié)的棧空間后,問題隨之而來。為了解決這個問題,每個go函數(shù)在函數(shù)入口處都會有一小段代碼(called prologue),這段代碼會檢查是否用光了已分配的??臻g,如果用光了,這段代碼會調用morestack函數(shù)。
morestack函數(shù)會分配一段新內存用作??臻g,接下來它會將有關棧的各種數(shù)據(jù)信息寫入棧底的一個struct中(譯注:下圖中Stack info),包括上一段棧的地址。有點我們擁有了一個新的棧段(stack segment),我們將重啟goroutine,從導致??臻g用光的那個函數(shù)(譯注:下圖中的Foobar)開始執(zhí)行。這就是所謂的“棧分裂 (stack split)”。
下面的棧示意圖剛好是我們進行棧分裂后的情形:
在新棧的底部,我們插入了一個棧入口函數(shù)lessstack。我們不會調用該函數(shù),設置這個函數(shù)就是用于我們從那個導致我們用光棧空間的函數(shù)(譯 注:Foobar)返回時用的。當那個函數(shù)(譯注:Foobar)返回時,我們回到lessstack(這個棧幀),lessstack會查找 stack底部的那個struct,并調整棧指針(stack pointer),使得我們返回到前一段??臻g。這樣做之后,我們就可以將這個新棧段(stack segment)釋放掉,并繼續(xù)執(zhí)行我們的程序了。
四、分段棧(Segmented stacks)的問題
分段棧給了我們具備按需伸縮能力的棧。程序員們無需擔心計算棧的大小了,啟動一個新的goroutine代價低廉并且程序員不會知道棧將增長多 大。
這就是直到目前Go語言處理stack增長的方法,但是這個方法有個瑕疵。那就是??s小會是一個相對代價高昂的操作。如果你在一個循環(huán)遇到棧分裂 (stack split),你會最有感觸。一個函數(shù)會增加??臻g,做棧分裂,返回并釋放棧段(stack segment)。如果你在一個循環(huán)中進行這些,你會付出很大的代價(性能方面)。
這就是所謂的“hot split”問題。它也是Go核心開發(fā)組更換到一個新的棧管理方案-棧拷貝(stack copying)的主要原因。
五、??截?stack copying)
??截惓跏茧A段與分段棧類似。goroutine在棧上運行著,當用光??臻g,它遇到與舊方案中相同的棧溢出檢查。但是與舊方案采用的保留一個返 回前一段棧的link不同,新方案創(chuàng)建一個兩倍于原stack大小的新stack,并將舊??截惖狡渲?。這意味著當棧實際使用的空間縮小為原先的 大小時,go運行時不用做任何事情。??s小是一個無任何代價的操作。此外,當棧再次增長時,運行時也無需做任何事情,我們只需要重用之前分配的空 閑空間即可。
六、棧是怎么拷貝的
拷貝棧聽起來簡單,但實際上它是一件有難度的事情。因為Go中棧上的變量都有自己的地址,一旦你擁有指向棧上變量的指針,這種情況下你就無法如你 所愿。當你移動棧時,指向原棧的指針都將變?yōu)闊o效指針。
幸運的是,只有在棧上分配的指針才能指向棧上的地址。這點對于內存安全是極其必要的,否則,程序可能會訪問到已不再使用了的棧上的地址。
由于我們需要知道那些需要被垃圾收集器回收的指針的位置,因此我們知道棧上哪些部分是指針。當我們移動棧時,我們可以更新棧里地指針使其指向新的 目標地址,并且所有相關的指針都要被照顧到。
由于我們使用垃圾回收的信息來協(xié)助完成??截悾虼怂谐霈F(xiàn)在棧上的函數(shù)都必須具備這些信息。但事情不總是這樣的。因為Go運行時的大部分代碼是 用C編寫的,大量的運行時調用沒有指針信息可用,這樣就無法進行拷貝。一旦這種情況發(fā)生,我們又不得不退回到分段棧方案,并接受為其付出的高昂代 價。
這就是當前Go運行時開發(fā)者大規(guī)模重寫Go runtime的原因。那些無法用Go重寫的代碼,比如調度器和垃圾收集器的內核,將在一個特殊的棧上執(zhí)行,這個特殊棧的size由runtime開發(fā)者 單獨計算確定。
除了讓??截惓蔀榭赡苤?,這個方法還會使得我們在未來能夠實現(xiàn)出并發(fā)垃圾回收等特性。
七、關于虛擬內存
另外一種不同的棧處理方式就是在虛擬內存中分配大內存段。由于物理內存只是在真正使用時才會被分配,因此看起來好似你可以分配一個大內存段并讓操 作系統(tǒng)處理它。下面是這種方法的一些問題
首先,32位系統(tǒng)只能支持4G字節(jié)虛擬內存,并且應用只能用到其中的3G空間。由于同時運行百萬goroutines的情況并不少見,因此你很可 能用光虛擬內存,即便我們假設每個goroutine的stack只有8K。
第二,然而我們可以在64位系統(tǒng)中分配大內存,它依賴于過量內存使用。所謂過量使用是指當你分配的內存大小超出物理內存大小時,依賴操作系統(tǒng)保證 在需要時能夠分配出物理內存。然而,允許過量使用可能會導致一些風險。由于一些進程分配了超出機器物理內存大小的內存,如果這些進程使用更多內存 時,操作系統(tǒng)將不得不為它們補充分配內存。這會導致操作系統(tǒng)將一些內存段放入磁盤緩存,這常常會增加不可預測的處理延遲。正是考慮到這個原因,一 些新系統(tǒng)關閉了對過量使用的支持。
“Go語言中如何對棧進行處理”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注創(chuàng)新互聯(lián)網站,小編將為大家輸出更多高質量的實用文章!
新聞名稱:Go語言中如何對棧進行處理
標題網址:http://chinadenli.net/article12/iegddc.html
成都網站建設公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)、建站公司、網站導航、全網營銷推廣、外貿建站、網站收錄
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)