Golang中的調(diào)度器GPM模型詳解
Golang 調(diào)度器 GPM模型
1 多進(jìn)程/線程時(shí)代有了調(diào)度器需求
在多進(jìn)程/多線程的操作系統(tǒng)中,就解決了阻塞的問題,因?yàn)橐粋€(gè)進(jìn)程阻塞cpu可以立刻切換到其他進(jìn)程中去執(zhí)行,而且調(diào)度cpu的算法可以保證在運(yùn)行的進(jìn)程都可以被分配到cpu的運(yùn)行時(shí)間片。這樣從宏觀來看,似乎多個(gè)進(jìn)程是在同時(shí)被運(yùn)行。
上圖為一個(gè)CPU通過調(diào)度器切換CPU時(shí)間軸的情景。如果未來滿足宏觀上每個(gè)進(jìn)程/線程是一起執(zhí)行的,則CPU必須切換,每個(gè)進(jìn)程會(huì)被分配到一個(gè)時(shí)間片中。
但新的問題就又出現(xiàn)了,進(jìn)程擁有太多的資源,進(jìn)程的創(chuàng)建、切換、銷毀,都會(huì)占用很長(zhǎng)的時(shí)間,CPU雖然利用起來了,但如果進(jìn)程過多,CPU有很大的一部分都被用來進(jìn)行進(jìn)程調(diào)度了。如下圖所示:
對(duì)于Linux操作系統(tǒng)來言,CPU對(duì)進(jìn)程和線程的態(tài)度是一樣的,如圖1.3所示,如果系統(tǒng)的CPU數(shù)量過少,而進(jìn)程/線程數(shù)量比較龐大,則相互切換的頻率也就會(huì)很高,其中中間的切換成本越來越大。這一部分的性能消耗實(shí)際上是沒有做在對(duì)程序有用的計(jì)算算力上,所以盡管線程看起來很美好,但實(shí)際上多線程開發(fā)設(shè)計(jì)會(huì)變得更加復(fù)雜,開發(fā)者要考慮很多同步競(jìng)爭(zhēng)的問題,如鎖、資源競(jìng)爭(zhēng)、同步?jīng)_突等。
2 協(xié)程來提高CPU利用率
那么如何才能提高CPU的利用率呢?多進(jìn)程、多線程已經(jīng)提高了系統(tǒng)的并發(fā)能力,但是在當(dāng)今互聯(lián)網(wǎng)高并發(fā)場(chǎng)景下,為每個(gè)任務(wù)都創(chuàng)建一個(gè)線程是不現(xiàn)實(shí)的,因?yàn)檫@樣就會(huì)出現(xiàn)極大量的線程同時(shí)運(yùn)行,不僅切換頻率高,也會(huì)消耗大量的內(nèi)存:進(jìn)程虛擬內(nèi)存會(huì)占用4GB(32位操作系統(tǒng)),而線程也要大約4MB。大量的進(jìn)程或線程出現(xiàn)了以下兩個(gè)新的問題。
- (1)高內(nèi)存占用。
- (2)調(diào)度的高消耗CPU。
工程師發(fā)現(xiàn)其實(shí)可以把一個(gè)線程分為“內(nèi)核態(tài)”和“用戶態(tài)”兩種形態(tài)的線程。所謂用戶態(tài)線程就是把內(nèi)核態(tài)的線程在用戶態(tài)實(shí)現(xiàn)了一遍而已,目的是更輕量化(更少的內(nèi)存占用、更少的隔離、更快的調(diào)度)和更高的可控性(可以自己控制調(diào)度器)。用戶態(tài)中的所有東西內(nèi)核態(tài)都看得見,只是對(duì)于內(nèi)核而言用戶態(tài)線程只是一堆內(nèi)存數(shù)據(jù)而已。
一個(gè)用戶態(tài)線程必須綁定一個(gè)內(nèi)核態(tài)線程,但是CPU并不知道有用戶態(tài)線程的存在,它只知道它運(yùn)行的是一個(gè)內(nèi)核態(tài)線程(Linux的PCB進(jìn)程控制塊),如下圖所示:
如果將線程再進(jìn)行細(xì)化,內(nèi)核線程依然叫 線程(Thread)
,而用戶線程則叫 協(xié)程(Co-routine)
。操作系統(tǒng)層面的線程就是所謂的內(nèi)核態(tài)線程,用戶態(tài)線程則多種多樣,只要能滿足在同一個(gè)內(nèi)核線程上執(zhí)行多個(gè)任務(wù),例如Co-routine、Go的Goroutine、C#的Task等。
既然一個(gè)協(xié)程可以綁定一個(gè)線程,那么能不能多個(gè)協(xié)程綁定一個(gè)或者多個(gè)線程呢?接下來有3種協(xié)程和線程的映射關(guān)系,它們分別是 N : 1
關(guān)系、 1 : 1
關(guān)系和 M : N
關(guān)系。
3 N比1關(guān)系
N個(gè)協(xié)程綁定1個(gè)線程,優(yōu)點(diǎn)就是協(xié)程在用戶態(tài)線程即完成切換,不會(huì)陷入內(nèi)核態(tài),這種切換非常輕量快速,但缺點(diǎn)也很明顯,1個(gè)進(jìn)程的所有協(xié)程都綁定在1個(gè)線程上,如圖所示。
N:1關(guān)系面臨的幾個(gè)問題如下:
- (1) 某個(gè)程序用不了硬件的多核加速能力。
- (2) 某一個(gè)協(xié)程阻塞,會(huì)造成線程阻塞,本進(jìn)程的其他協(xié)程都無法執(zhí)行了,進(jìn)而導(dǎo)致沒有任何并發(fā)能力。
4 1比1關(guān)系
1個(gè)協(xié)程綁定1個(gè)線程,這種方式最容易實(shí)現(xiàn)。協(xié)程的調(diào)度都由CPU完成了,雖然不存在N:1的缺點(diǎn),但是協(xié)程的創(chuàng)建、刪除和切換的代價(jià)都由CPU完成,成本和代價(jià)略顯昂貴。協(xié)程和線程的1:1關(guān)系如圖所示。
5 M比N關(guān)系
M個(gè)協(xié)程綁定1個(gè)線程,是 N: 1
和 1 : 1
類型的結(jié)合,克服了以上兩種模型的缺點(diǎn),但實(shí)現(xiàn)起來最為復(fù)雜。同一個(gè)調(diào)度器上掛載M個(gè)協(xié)程,調(diào)度器下游則是多個(gè)CPU核心資源。協(xié)程跟線程是有區(qū)別的,線程由CPU調(diào)度是搶占式的,協(xié)程由用戶態(tài)調(diào)度是協(xié)作式的,一個(gè)協(xié)程讓出CPU后,才執(zhí)行下一個(gè)協(xié)程,所以針對(duì) M : N
模型的中間層的調(diào)度器設(shè)計(jì)就變得尤為重要,提高線程和協(xié)程的綁定關(guān)系和執(zhí)行效率也變?yōu)椴煌Z言在設(shè)計(jì)調(diào)度器時(shí)的優(yōu)先目標(biāo)。
6 Go語言的協(xié)程goroutine
Go語言為了提供更容易使用的并發(fā)方法,使用了Goroutine和Channel。Goroutine來自協(xié)程的概念,讓一組可復(fù)用的函數(shù)運(yùn)行在一組線程之上,即使有協(xié)程阻塞,該線程的其他協(xié)程也可以被runtime調(diào)度,從而轉(zhuǎn)移到其他可運(yùn)行的線程上。最關(guān)鍵的是,程序員看不到這些底層的細(xì)節(jié),這就降低了編程的難度,提供了更容易的并發(fā)。
在Go語言中,協(xié)程被稱為Goroutine,它非常輕量,一個(gè)Goroutine只占幾KB,并且這幾KB就足夠Goroutine運(yùn)行完,這就能在有限的內(nèi)存空間內(nèi)支持大量Goroutine,從而支持更多的并發(fā)。雖然一個(gè)Goroutine的棧只占幾KB,但實(shí)際是可伸縮的,如果需要更多內(nèi)存,則runtime會(huì)自動(dòng)為Goroutine分配。
Goroutine的特點(diǎn),占用內(nèi)存更?。◣譑B)和調(diào)度更靈活(runtime調(diào)度)。
7 被廢棄的goroutine調(diào)度器
Go語言目前使用的調(diào)度器是2012年重新設(shè)計(jì)的,因?yàn)橹暗恼{(diào)度器性能存在問題,所以使用4年就被廢棄了,那么先來分析一下被廢棄的調(diào)度器是如何運(yùn)作的。
通常用符號(hào)G表示Goroutine,用M表示線程。接下來有關(guān)調(diào)度器的內(nèi)容均采用圖1.8所示的符號(hào)來統(tǒng)一表達(dá)。
早期的調(diào)度器是基于M:N的基礎(chǔ)上實(shí)現(xiàn)的,圖1.9是一個(gè)概要圖形,所有的協(xié)程,也就是G都會(huì)被放在一個(gè)全局的Go協(xié)程隊(duì)列中,在全局隊(duì)列的外面由于是多個(gè)M的共享資源,所以會(huì)加上一個(gè)用于同步及互斥作用的鎖。
M想要執(zhí)行、放回G都必須訪問全局G隊(duì)列,并且M有多個(gè),即多線程訪問同一資源需要加鎖進(jìn)行保證互斥/同步,所以全局G隊(duì)列是由互斥鎖進(jìn)行保護(hù)的。
不難分析出來,老調(diào)度器有以下幾個(gè)缺點(diǎn):
- (1) 創(chuàng)建、銷毀、調(diào)度G都需要每個(gè)M獲取鎖,這就形成了激烈的鎖競(jìng)爭(zhēng)。
- (2) M轉(zhuǎn)移G會(huì)造成延遲和額外的系統(tǒng)負(fù)載。例如當(dāng)G中包含創(chuàng)建新協(xié)程的時(shí)候,M創(chuàng)建了G′,為了繼續(xù)執(zhí)行G,需要把G′交給M2(假如被分配到)執(zhí)行,也造成了很差的局部性,因?yàn)镚′和G是相關(guān)的,最好放在M上執(zhí)行,而不是其他M2,如圖1.10所示
- (3) 系統(tǒng)調(diào)用(CPU在M之間的切換)導(dǎo)致頻繁的線程阻塞和取消阻塞操作增加了系統(tǒng)開銷。
8 Goroutine調(diào)度器的GMP模型的設(shè)計(jì)思想
面對(duì)之前調(diào)度器的問題,Go設(shè)計(jì)了新的調(diào)度器。在新調(diào)度器中,除了 M(線程)
和 G(協(xié)程)
,又引進(jìn)了 P(處理器)
。
處理器包含了運(yùn)行Goroutine的資源,如果線程想運(yùn)行Goroutine,必須先獲取P,P中還包含了可運(yùn)行的G隊(duì)列。
9 GPM模型
在Go中,線程是運(yùn)行Goroutine的實(shí)體,調(diào)度器的功能是把可運(yùn)行的Goroutine分配到工作線程上。
在GPM模型中有以下幾個(gè)重要的概念,如圖1.12所示。
- (1)全局隊(duì)列(Global Queue): 存放等待運(yùn)行的G。全局隊(duì)列可能被任意的P去獲取里面的G,所以全局隊(duì)列相當(dāng)于整個(gè)模型中的全局資源,那么自然對(duì)于隊(duì)列的讀寫操作是要加入互斥動(dòng)作的。
- (2)P的本地隊(duì)列: 同全局隊(duì)列類似,存放的也是等待運(yùn)行的G,但存放的數(shù)量有限,不超過256個(gè)。新建G′時(shí),G′優(yōu)先加入P的本地隊(duì)列,如果隊(duì)列滿了,則會(huì)把本地隊(duì)列中一半的G移動(dòng)到全局隊(duì)列。
- (3)P列表: 所有的P都在程序啟動(dòng)時(shí)創(chuàng)建,并保存在數(shù)組中,最多有GOMAXPROCS(可配置)個(gè)。
- (4)M: 線程想運(yùn)行任務(wù)就得獲取P,從P的本地隊(duì)列獲取G,當(dāng)P隊(duì)列為空時(shí),M也會(huì)嘗試從全局隊(duì)列獲得一批G放到P的本地隊(duì)列,或從其他P的本地隊(duì)列“偷”一半放到自己P的本地隊(duì)列。M運(yùn)行G,G執(zhí)行之后,M會(huì)從P獲取下一個(gè)G,不斷重復(fù)下去。
Goroutine調(diào)度器和OS調(diào)度器是通過M結(jié)合起來的,每個(gè)M都代表了1個(gè)內(nèi)核線程,OS調(diào)度器負(fù)責(zé)把內(nèi)核線程分配到CPU的核上執(zhí)行。
10 有關(guān)P和M個(gè)數(shù)的問題
- (1) P的數(shù)量由啟動(dòng)時(shí)環(huán)境變量
$GOMAXPROCS
或者由runtime
的方法GOMAXPROCS( )
決定。這意味著在程序執(zhí)行的任意時(shí)刻都只有$GOMAXPROCS
個(gè) Goroutine在 同時(shí)運(yùn)行。 - (2)M的數(shù)量由Go語言本身的限制決定,Go程序啟動(dòng)時(shí)會(huì)設(shè)置M的最大數(shù)量,默認(rèn)為10000個(gè),但是內(nèi)核很難支持這么多的線程數(shù),所以這個(gè)限制可以忽略。
runtime/deBug
中的SetMaxThreads( )
函數(shù)可設(shè)置M的最大數(shù)量,當(dāng)一個(gè)M阻塞了時(shí)會(huì)創(chuàng)建新的M。
M與P的數(shù)量沒有絕對(duì)關(guān)系,一個(gè)M阻塞,P就會(huì)去創(chuàng)建或者切換另一個(gè)M,所以,即使P的默認(rèn)數(shù)量是1,也有可能會(huì)創(chuàng)建很多個(gè)M出來。
11 有關(guān)P和M何時(shí)被創(chuàng)建
- (1) P創(chuàng)建的時(shí)機(jī)在確定了P的最大數(shù)量n后,運(yùn)行時(shí)系統(tǒng)會(huì)根據(jù)這個(gè)數(shù)量創(chuàng)建n個(gè)P。
- (2) M創(chuàng)建的時(shí)機(jī)是在當(dāng)沒有足夠的M來關(guān)聯(lián)P并運(yùn)行其中可運(yùn)行的G的時(shí)候。例如所有的M此時(shí)都阻塞住了,而P中還有很多就緒任務(wù),就會(huì)去尋找空閑的M,如果此時(shí)沒有空閑的M,就會(huì)去創(chuàng)建新的M。
12 調(diào)度器的設(shè)計(jì)策略
策略一:復(fù)用線程
避免頻繁地創(chuàng)建、銷毀線程,而是對(duì)線程的復(fù)用。
1)偷取(Work Stealing)機(jī)制
當(dāng)本線程無可運(yùn)行的G時(shí),嘗試從其他線程綁定的P偷取G,而不是銷毀線程,如圖1.13所示
這里需要注意的是,偷取的動(dòng)作一定是由P發(fā)起的,而非M,因?yàn)镻的數(shù)量是固定的,如果一個(gè)M得不到一個(gè)P,那么這個(gè)M是沒有執(zhí)行的本地隊(duì)列的,更談不上向其他的P隊(duì)列偷取了。
2)移交(Hand Off)機(jī)制
當(dāng)本線程因?yàn)镚進(jìn)行系統(tǒng)調(diào)用阻塞時(shí),線程會(huì)釋放綁定的P,把P轉(zhuǎn)移給其他空閑的線程執(zhí)行,如圖1.14所示,此時(shí)若在M1的GPM組合中,G1正在被調(diào)度,并且已經(jīng)發(fā)生了阻塞,則這個(gè)時(shí)候就會(huì)觸發(fā)移交的設(shè)計(jì)機(jī)制。GPM模型為了更大程度地利用M和P的性能,不會(huì)讓一個(gè)P永遠(yuǎn)被一個(gè)阻塞的G1耽誤之后的工作,所以遇見這種情況的時(shí)候,移交機(jī)制的設(shè)計(jì)理念是應(yīng)該立刻將此時(shí)的P釋放出來
如圖1.15所示,為了釋放P,所以將P和M1、G1分離,M1由于正在執(zhí)行當(dāng)前的G1,全部的程序??臻g均在M1中保存,所以M1此時(shí)應(yīng)該與G1一同進(jìn)入阻塞的狀態(tài),但是已經(jīng)被釋放的P需要跟另一個(gè)M進(jìn)行綁定,所以就會(huì)選擇一個(gè)M3(如果此時(shí)沒有M3,則會(huì)創(chuàng)建一個(gè)新的或者喚醒一個(gè)正在睡眠的M)進(jìn)行綁定,這樣新的P就會(huì)繼續(xù)工作,接收新的G或者從其他的隊(duì)列中實(shí)施偷取機(jī)制。
策略二:利用并行
GOMAXPROCS
設(shè)置P的數(shù)量,最多有 GOMAXPROCS
個(gè)線程分布在多個(gè)CPU上同時(shí)運(yùn)行。 GOMAXPROCS
也限制了并發(fā)的程度,例如 GOMAXPROCS=核數(shù)/2
,表示最多利用一半的CPU核進(jìn)行并行。
策略三:搶占
在Co-routine中要等待一個(gè)協(xié)程主動(dòng)讓出CPU才執(zhí)行下一個(gè)協(xié)程,在Go中,一個(gè)Goroutine最多占用CPU 10ms,防止其他Goroutine無資源可用,這就是Goroutine不同于Co-routine的一個(gè)地方。
- Co-routine(C語言中的協(xié)程),用戶態(tài)線程。
- coroutine 是基于 ucontext 的一個(gè) C 語言協(xié)程庫實(shí)現(xiàn)
策略四:全局G隊(duì)列
在新的調(diào)度器中依然有全局G隊(duì)列,但功能已經(jīng)被弱化了,當(dāng)M執(zhí)行偷取,但從其他P偷不到G時(shí),它可以從全局G隊(duì)列獲取G。
13 go func() 調(diào)度流程
如果執(zhí)行一行代碼 go func( )
,則在GPM模型上的概念里會(huì)執(zhí)行哪些操作。
(1)通過 go func( )
創(chuàng)建一個(gè)Goroutine,
(2)有兩個(gè)存儲(chǔ)G的隊(duì)列,一個(gè)是局部調(diào)度器P的本地隊(duì)列,另一個(gè)是全局G隊(duì)列。新創(chuàng)建的G會(huì)先保存在P的本地隊(duì)列中,如果P的本地隊(duì)列已經(jīng)滿了,就會(huì)保存在全局的隊(duì)列中,如圖1.19所示。
(3)G只能運(yùn)行在M中,一個(gè)M必須持有一個(gè)P,M與P是1:1的關(guān)系。M會(huì)從P的本地隊(duì)列彈出一個(gè)可執(zhí)行狀態(tài)的G來執(zhí)行,如果P的本地隊(duì)列為空,則會(huì)從全局隊(duì)列進(jìn)行獲取,如果從全局隊(duì)列獲取不到,則會(huì)向其他的MP組合偷取一個(gè)可執(zhí)行的G來執(zhí)行,如圖1.20所示。
(4)一個(gè)M調(diào)度G執(zhí)行的過程是一個(gè)循環(huán)機(jī)制,如圖1.21所示。
(5)當(dāng)M執(zhí)行某一個(gè)G時(shí)如果發(fā)生了syscall或者其余阻塞操作,則M會(huì)阻塞,如果當(dāng)前有一些G在執(zhí)行,runtime則會(huì)把這個(gè)線程M從P中移除(Detach),然后創(chuàng)建一個(gè)新的操作系統(tǒng)線程(如果有空閑的線程可用就復(fù)用空閑線程)來服務(wù)于這個(gè)P。
(6)當(dāng)M系統(tǒng)調(diào)用結(jié)束時(shí),這個(gè)G會(huì)嘗試獲取一個(gè)空閑的P執(zhí)行,并放入這個(gè)P的本地隊(duì)列。如果獲取不到P,則這個(gè)線程M會(huì)變成休眠狀態(tài),加入空閑線程中,然后這個(gè)G會(huì)被放入全局隊(duì)列中。
14 調(diào)度器的生命周期
在Go語言調(diào)度器的GPM模型中還有兩個(gè)比較特殊的角色,它們分別是M0和G0。
M0
- (1)啟動(dòng)程序后的編號(hào)為0的主線程。
- (2)在全局命令runtime.m0中,不需要在heap堆上分配。
- (3)負(fù)責(zé)執(zhí)行初始化操作和啟動(dòng)第1個(gè)G。
- (4)啟動(dòng)第1個(gè)G后,M0就和其他的M一樣了。
G0
- (1)每次啟動(dòng)一個(gè)M,創(chuàng)建的第1個(gè)Goroutine就是G0。
- (2)G0僅用于負(fù)責(zé)調(diào)度G。
- (3)G0不指向任何可執(zhí)行的函數(shù)。
- (4)每個(gè)M都會(huì)有一個(gè)自己的G0。
- (5)在調(diào)度或系統(tǒng)調(diào)度時(shí),會(huì)使用M切換到G0,再通過G0調(diào)度。
- (6)M0的G0會(huì)放在全局空間。
一個(gè)Goroutine的創(chuàng)建周期如果加上M0和G0的角色,則整體的流程如圖1.24所示。
下面跟蹤一段代碼,對(duì)調(diào)度器里面的結(jié)構(gòu)做一個(gè)分析,代碼如下:
package main import "fmt" func main() { fmt.Println("Hello world") }
整體的分析過程如下:
- (1)runtime創(chuàng)建最初的線程
M0
和Goroutine G0
,并把二者關(guān)聯(lián)。 - (2)調(diào)度器初始化:初始化M0、棧、垃圾回收,以及創(chuàng)建和初始化由
GOMAXPROCS
個(gè)P
構(gòu)成的P列表
,如圖1.25所示。 - (3)示例代碼中的
main( )
函數(shù)是main.main
,runtime中也有1個(gè)main()函數(shù)runtime.main
,代碼經(jīng)過編譯后,runtime.main
會(huì)調(diào)用main.main
,程序啟動(dòng)時(shí)會(huì)為runtime.main
創(chuàng)建Goroutine,稱為Main Goroutine
,然后把Main Goroutine
加入P的本地隊(duì)列。 - (4)啟動(dòng)M0,M0已經(jīng)綁定了P,會(huì)從P的本地隊(duì)列獲取G,并獲取
Main Goroutine
。 - (5)G擁有棧,M根據(jù)G中的棧信息和調(diào)度信息設(shè)置運(yùn)行環(huán)境。
- (6)M運(yùn)行G。
- (7)G退出,再次回到M獲取可運(yùn)行的G,這樣重復(fù)下去,直到
main.main
退出,runtime.main
執(zhí)行Defer和Panic處理,或調(diào)用runtime.exit
退出程序。
調(diào)度器的生命周期幾乎占滿了一個(gè)Go程序的一生, runtime.main
的Goroutine執(zhí)行之前都是為調(diào)度器做準(zhǔn)備工作, runtime.main
的Goroutine運(yùn)行才是調(diào)度器的真正開始,直到 runtime.main
結(jié)束而結(jié)束。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Go語言計(jì)算兩個(gè)經(jīng)度和緯度之間距離的方法
這篇文章主要介紹了Go語言計(jì)算兩個(gè)經(jīng)度和緯度之間距離的方法,涉及Go語言相關(guān)數(shù)學(xué)函數(shù)的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02Golang實(shí)現(xiàn)優(yōu)雅的將struct轉(zhuǎn)換為map
在項(xiàng)目實(shí)踐中,有時(shí)候我們需要將struct結(jié)構(gòu)體轉(zhuǎn)為map映射表,然后基于map做數(shù)據(jù)裁剪或操作。那么下面我來介紹下常用的兩種轉(zhuǎn)換方式,希望對(duì)大家有所幫助2023-01-01一文詳解go中如何實(shí)現(xiàn)定時(shí)任務(wù)
定時(shí)任務(wù)是指按照預(yù)定的時(shí)間間隔或特定時(shí)間點(diǎn)自動(dòng)執(zhí)行的計(jì)劃任務(wù)或操作,這篇文章主要為大家詳細(xì)介紹了go中是如何實(shí)現(xiàn)定時(shí)任務(wù)的,感興趣的可以了解下2023-11-11一站式解決方案:在Windows和Linux上快速搭建Go語言開發(fā)環(huán)境
本文將介紹如何在Windows和Linux操作系統(tǒng)下搭建Go語言開發(fā)環(huán)境,以幫助您更高效地進(jìn)行Go語言開發(fā),需要的朋友可以參考下2023-10-10Go語言編程通過dwarf獲取內(nèi)聯(lián)函數(shù)
這篇文章主要為大家介紹了Go語言編程通過dwarf獲取內(nèi)聯(lián)函數(shù)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Golang使用gzip壓縮字符減少redis等存儲(chǔ)占用的實(shí)現(xiàn)
本文主要介紹了Golang使用gzip壓縮字符減少redis等存儲(chǔ)占用的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01