真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

go語言mutex的簡(jiǎn)單介紹

go語言中指針的使用場(chǎng)景?

如果該函數(shù)會(huì)修改receiver,此時(shí)一定要用指針

成都創(chuàng)新互聯(lián)公司長(zhǎng)期為成百上千家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為潼南企業(yè)提供專業(yè)的網(wǎng)站設(shè)計(jì)制作、做網(wǎng)站,潼南網(wǎng)站改版等技術(shù)服務(wù)。擁有十多年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。

如果receiver是 struct 并且包含互斥類型 sync.Mutex ,或者是類似的同步變量,receiver必須是指針,這樣可以避免對(duì)象拷貝

如果receiver是較大的 struct 或者 array ,使用指針則更加高效。多大才算大?假設(shè)struct內(nèi)所有成員都要作為函數(shù)變量傳進(jìn)去,如果覺得這時(shí)數(shù)據(jù)太多,就是struct太大

如果receiver是 struct , array 或者 slice ,并且其中某個(gè)element指向了某個(gè)可變量,則這個(gè)時(shí)候receiver選指針會(huì)使代碼的意圖更加明顯

如果receiver使較小的 struct 或者 array ,并且其變量都是些不變量、常量,例如 time.Time ,value receiver更加適合,因?yàn)関alue receiver可以減少需要回收的垃圾量。

golang是自動(dòng)釋放內(nèi)存嗎

golang是一門自帶垃圾回收的語言,它的內(nèi)存分配器和tmalloc(thread-caching malloc)很像,大多數(shù)情況下是不需要用戶自己管理內(nèi)存的。最近了解了一下golang內(nèi)存管理,寫出來分享一下,不正確的地方請(qǐng)大佬們指出。

1.內(nèi)存池:

應(yīng)該有一個(gè)主要管理內(nèi)存分配的部分,向系統(tǒng)申請(qǐng)大塊內(nèi)存,然后進(jìn)行管理和分配。

2.垃圾回收:

當(dāng)分配的內(nèi)存使用完之后,不直接歸還給系統(tǒng),而是歸還給內(nèi)存池,方便進(jìn)行下一次復(fù)用。至于垃圾回收選擇標(biāo)記回收,還是分代回收算法應(yīng)該符合語言設(shè)計(jì)初衷吧。

3.大小切分:

使用單獨(dú)的數(shù)組或者鏈表,把需要申請(qǐng)的內(nèi)存大小向上取整,直接從這個(gè)數(shù)組或鏈表拿出對(duì)應(yīng)的大小內(nèi)存塊,方便分配內(nèi)存。大的對(duì)象以頁申請(qǐng)內(nèi)存,小的對(duì)象以塊來申請(qǐng),避免內(nèi)存碎片,提高內(nèi)存使用率。

4.多線程管理:

每個(gè)線程應(yīng)該有自己的內(nèi)存塊,這樣避免同時(shí)訪問共享區(qū)的時(shí)候加鎖,提升語言的并發(fā)性,線程之間通信使用消息隊(duì)列的形式,一定不要使用共享內(nèi)存的方式。提供全局性的分配鏈,如果線程內(nèi)存不夠用了,可向分配鏈申請(qǐng)內(nèi)存。

這樣的內(nèi)存分配設(shè)計(jì)涵蓋了大部分語言的,上面的想法其實(shí)是把golang語言內(nèi)存分配抽象出來。其實(shí)Java語言也可以以同樣的方式理解。內(nèi)存池就是JVM堆,主要負(fù)責(zé)申請(qǐng)大塊內(nèi)存;多線程管理方面是使用棧內(nèi)存,每個(gè)線程有自己獨(dú)立的棧內(nèi)存進(jìn)行管理。

golang內(nèi)存分配器

golang內(nèi)存分配器主要包含三個(gè)數(shù)據(jù)結(jié)構(gòu):MHeap,MCentral以及MCache

1.MHeap:分配堆,主要是負(fù)責(zé)向系統(tǒng)申請(qǐng)大塊的內(nèi)存,為下層MCentral和MCache提供內(nèi)存服務(wù)。他管理的基本單位是MSpan(若干連續(xù)內(nèi)存頁的數(shù)據(jù)結(jié)構(gòu))

type MSpan struct

{

MSpan ? *next;

MSpan ? *prev;

PageId ?start; ?// 開始的頁號(hào)

uintptr ? npages; // 頁數(shù)

…..

};

可以看出MSpan是一個(gè)雙端鏈表的形式,里面存儲(chǔ)了它的一些位置信息。

通過一個(gè)基地址+(頁號(hào)*頁大小),就可以定位到這個(gè)MSpan的實(shí)際內(nèi)存空間。

type MHeap struct

{

lock ?mutex;

free ?[_MaxMHeapList] mSpanList ?// free lists of given length

freelarge mSpanList ? // free lists length = _MaxMHeapList

busy ?[_MaxMHeapList] mSpanList ? // busy lists of large objects of given length

busylarge mSpanList

};

free數(shù)組以span為序號(hào)管理多個(gè)鏈表。當(dāng)central需要時(shí),只需從free找到頁數(shù)合適的鏈表。large鏈表用于保存所有超出free和busy頁數(shù)限制的MSpan。

MHeap示意圖:

2.MCache:運(yùn)行時(shí)分配池,不針對(duì)全局,而是每個(gè)線程都有自己的局部?jī)?nèi)存緩存MCache,他是實(shí)現(xiàn)goroutine高并發(fā)的重要因素,因?yàn)榉峙湫?duì)象可直接從MCache中分配,不用加鎖,提升了并發(fā)效率。

type MCache struct

{

tiny ? byte*; ?// Allocator cache for tiny objects w/o pointers.

tinysize ? uintptr;

alloc[NumSizeClasses] MSpan*; ?// spans to allocate from

};

盡可能將微小對(duì)象組合到一個(gè)tiny塊中,提高性能。

alloc[]用于分配對(duì)象,如果沒有了,則可以向?qū)?yīng)的MCentral獲取新的Span進(jìn)行操作。

線程中分配小對(duì)象(16~32K)的過程:

對(duì)于

size 介于 16 ~ 32K byte 的內(nèi)存分配先計(jì)算應(yīng)該分配的 sizeclass,然后去 mcache 里面

alloc[sizeclass] 申請(qǐng),如果 mcache.alloc[sizeclass] 不足以申請(qǐng),則 mcache 向 mcentral

申請(qǐng)mcentral 給 mcache 分配完之后會(huì)判斷自己需不需要擴(kuò)充,如果需要?jiǎng)t想 mheap 申請(qǐng)。

每個(gè)線程內(nèi)申請(qǐng)內(nèi)存是逐級(jí)向上的,首先看MCache是否有足夠空間,沒有就像MCentral申請(qǐng),再?zèng)]有就像MHeap,MHeap向系統(tǒng)申請(qǐng)內(nèi)存空間。

3.MCentral:作為MHeap和MCache的承上啟下的連接。承上,從MHeap申請(qǐng)MSpan;啟下,將MSpan劃分為各種尺寸的對(duì)象提供給MCache使用。

type MCentral struct

{

lock mutex;

sizeClass int32;

noempty mSpanList;

empty mSpanList;

int32 nfree;

……

};

type mSpanList struct {

first *mSpan

last ?*mSpan

};

sizeclass: 也有成員 sizeclass,用于將MSpan進(jìn)行切分。

lock: 因?yàn)闀?huì)有多個(gè) P 過來競(jìng)爭(zhēng)。

nonempty: mspan 的雙向鏈表,當(dāng)前 mcentral 中可用的 mSpan list。

empty: 已經(jīng)被使用的,可以認(rèn)為是一種對(duì)所有 mSpan 的 track。MCentral存在于MHeap內(nèi)。

給對(duì)象 object 分配內(nèi)存的主要流程:

1.object size 32K,則使用 mheap 直接分配。

2.object size 16 byte,使用 mcache 的小對(duì)象分配器 tiny 直接分配。 (其實(shí) tiny 就是一個(gè)指針,暫且這么說吧。)

3.object size 16 byte size =32K byte 時(shí),先使用 mcache 中對(duì)應(yīng)的 size class 分配。

4.如果 mcache 對(duì)應(yīng)的 size class 的 span 已經(jīng)沒有可用的塊,則向 mcentral 請(qǐng)求。

5.如果 mcentral 也沒有可用的塊,則向 mheap 申請(qǐng),并切分。

6.如果 mheap 也沒有合適的 span,則想操作系統(tǒng)申請(qǐng)。

tcmalloc內(nèi)存分配器介紹

tcmalloc(thread-caching mallo)是google推出的一種內(nèi)存分配器。

具體策略:全局緩存堆和進(jìn)程的私有緩存。

1.對(duì)于一些小容量的內(nèi)存申請(qǐng)?jiān)囉眠M(jìn)程的私有緩存,私有緩存不足的時(shí)候可以再從全局緩存申請(qǐng)一部分作為私有緩存。

2.對(duì)于大容量的內(nèi)存申請(qǐng)則需要從全局緩存中進(jìn)行申請(qǐng)。而大小容量的邊界就是32k。緩存的組織方式是一個(gè)單鏈表數(shù)組,數(shù)組的每個(gè)元素是一個(gè)單鏈表,鏈表中的每個(gè)元素具有相同的大小。

golang語言中MHeap就是全局緩存堆,MCache作為線程私有緩存。

在文章開頭說過,內(nèi)存池就是利用MHeap實(shí)現(xiàn),大小切分則是在申請(qǐng)內(nèi)存的時(shí)候就做了,同時(shí)MCache分配內(nèi)存時(shí),可以用MCentral去取對(duì)應(yīng)的sizeClass,多線程管理方面則是通過MCache去實(shí)現(xiàn)。

總結(jié):

1.MHeap是一個(gè)全局變量,負(fù)責(zé)向系統(tǒng)申請(qǐng)內(nèi)存,mallocinit()函數(shù)進(jìn)行初始化。如果分配內(nèi)存對(duì)象大于32K直接向MHeap申請(qǐng)。

2.MCache線程級(jí)別管理內(nèi)存池,關(guān)聯(lián)結(jié)構(gòu)體P,主要是負(fù)責(zé)線程內(nèi)部?jī)?nèi)存申請(qǐng)。

3.MCentral連接MHeap與MCache的,MCache內(nèi)存不夠則向MCentral申請(qǐng),MCentral不夠時(shí)向MHeap申請(qǐng)內(nèi)存。

內(nèi)存對(duì)齊問題

1.平臺(tái)原因(移植原因): 不是所有的硬件平臺(tái)都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能

在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。

2.性能原因: 數(shù)據(jù)結(jié)構(gòu)應(yīng)該盡可能地在自然邊界上對(duì)齊。原因在于,為了訪問未對(duì)齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而對(duì)齊的內(nèi)存訪問僅需要一次訪問。(如果是對(duì)齊的,那么CPU不需要跨越兩個(gè)操作字,不是對(duì)齊的則需要訪問兩個(gè)操作字才能拼接出需要的內(nèi)存地址)

指針的大小一般是一個(gè)機(jī)器字的大小

通過Go語言的structlayout工具,可以得出下圖

這些類型在之前的 slice 、 map 、 interface 已經(jīng)介紹過了,也特意強(qiáng)調(diào)過,makehmap函數(shù)返回的是一個(gè)指針,因此map的對(duì)齊為一個(gè)機(jī)器字.

回頭看看 sync.pool的防止copy的空結(jié)構(gòu)體字段,也是放在第一位,破案了。

計(jì)算機(jī)結(jié)構(gòu)可能會(huì)要求內(nèi)存地址 進(jìn)行對(duì)齊;也就是說,一個(gè)變量的地址是一個(gè)因子的倍數(shù),也就是該變量的類型是對(duì)齊值。

函數(shù)Alignof接受一個(gè)表示任何類型變量的表達(dá)式作為參數(shù),并以字節(jié)為單位返回變量(類型)的對(duì)齊值。對(duì)于變量x:

這是因?yàn)閕nt64在bool之后未對(duì)齊。

它是32位對(duì)齊的,但不是64位對(duì)齊的,因?yàn)槲覀兪褂玫氖?2位系統(tǒng),因此實(shí)際上只是兩個(gè)32位值并排在一起。

● 內(nèi)存對(duì)齊是為了cpu更高效訪問內(nèi)存中數(shù)據(jù)

● 結(jié)構(gòu)體對(duì)齊依賴類型的大小保證和對(duì)齊保證

● 地址對(duì)齊保證是:如果類型 t 的對(duì)齊保證是 n,那么類型 t 的每個(gè)值的地址在運(yùn)行時(shí)必須是 n 的倍數(shù)。

● struct內(nèi)字段如果填充過多,可以嘗試重排,使字段排列更緊密,減少內(nèi)存浪費(fèi)

● 零大小字段要避免作為struct最后一個(gè)字段,會(huì)有內(nèi)存浪費(fèi)

● 32位系統(tǒng)上對(duì)64位字的原子訪問要保證其是8bytes對(duì)齊的;當(dāng)然如果不必要的 話,還是用加鎖(mutex)的方式更清晰簡(jiǎn)單

圖解go-內(nèi)存對(duì)齊

doc-pdf

Golang 游戲leaf系列(六) Go模塊

在 Golang 游戲leaf系列(一) 概述與示例 (下文簡(jiǎn)稱系列一)中,提到過Go模塊用于創(chuàng)建能夠被 Leaf 管理的 goroutine。Go模塊是對(duì)golang中g(shù)o提供一些額外功能。Go提供回調(diào)功能,LinearContext提供順序調(diào)用功能。善用 goroutine 能夠充分利用多核資源,Leaf 提供的 Go 機(jī)制解決了原生 goroutine 存在的一些問題:

我們來看一個(gè)例子(可以在 LeafServer 的模塊的 OnInit 方法中測(cè)試):

這里的 Go 方法接收 2 個(gè)函數(shù)作為參數(shù),第一個(gè)函數(shù)會(huì)被放置在一個(gè)新創(chuàng)建的 goroutine 中執(zhí)行,在其執(zhí)行完成之后,第二個(gè)函數(shù)會(huì)在當(dāng)前 goroutine 中被執(zhí)行。由此,我們可以看到變量 res 同一時(shí)刻總是只被一個(gè) goroutine 訪問,這就避免了同步機(jī)制的使用。Go 的設(shè)計(jì)使得 CPU 得到充分利用,避免操作阻塞當(dāng)前 goroutine,同時(shí)又無需為共享資源同步而憂心。

這里主動(dòng)調(diào)用了 d.Cb(-d.ChanCb) ,把這個(gè)回調(diào)取出來了。實(shí)際上,在skeleton.Run里會(huì)自己取這個(gè)通道

看一下源碼:

New方法,會(huì)生成指定緩沖長(zhǎng)度的ChanCb。然后調(diào)用Go方法就是先執(zhí)行第一個(gè)func,然后把第二個(gè)放到Cb里?,F(xiàn)在手動(dòng)造一個(gè)例子:

這里解釋一下,d.Go根據(jù)源碼來看,實(shí)際也是調(diào)用了一個(gè)協(xié)程。然后上面兩次d.Go并不能保證先后順序。目前的輸出結(jié)果是1+2那個(gè)先執(zhí)行了,把3寫入d.ChanCb,然后把3讀出來,繼續(xù)讀時(shí),d.ChanCb里沒有東西,阻塞了。然后1+1那個(gè)協(xié)程啟動(dòng)了,最后又讀到了2。

現(xiàn)在把time.Sleep(time.Second)的注釋解開,會(huì)是啥結(jié)果呢

這里執(zhí)行到time.Sleep睡著了,上面兩個(gè)d.Go仍然是不確定順序的,但是會(huì)各自的function先執(zhí)行掉,然后陸續(xù)把cb寫入d.ChanCb??催@次輸出,1+2先寫進(jìn)去的。所以最后執(zhí)行d.Cb時(shí),就把3先讀出來了。然后d.ChanCb的長(zhǎng)度為1,說明還有一個(gè),就是輸出2了。

另外,就是close時(shí)會(huì)判斷g.pendingGo

這個(gè)例子的意思很明顯,NewLinearContext這種方式,即使先調(diào)用的慢了半秒,它還是會(huì)先執(zhí)行完。

這里先是用了一個(gè)list,加入的時(shí)候用mutexLinearGo鎖了,都加到最后。然后新開協(xié)程去處理,讀的時(shí)候從最前面開始讀,也要用mutexLinearGo鎖。執(zhí)行的時(shí)候,也要上鎖mutexExecution,確保f()執(zhí)行完并且寫入g.ChanCb回調(diào),這個(gè)mutexExecution鎖才會(huì)解除。現(xiàn)在可以改造一個(gè)帶回調(diào)的例子:

結(jié)果說明,確實(shí)是2先被寫入了d.ChanCb。

golang sync.mutex 超時(shí)select

做了一個(gè)參考實(shí)例。假設(shè)某線程占用時(shí)間5秒,超時(shí)時(shí)間為2秒

func mian() {

lock := sync.Mutex{}

lock.Lock()

defer lock.Unlock()

timer := time.NewTimer(2 * time.Second)

end:=make(chan int)

go func() {

time.Sleep(5*time.Second)

fmt.Println("wait")

end-1

}()

select {

case -end:

case -timer.C:

}

fmt.Println("End")

}

Golang 語言深入理解:channel

本文是對(duì) Gopher 2017 中一個(gè)非常好的 Talk?: [Understanding Channel](GopherCon 2017: Kavya Joshi - Understanding Channels) 的學(xué)習(xí)筆記,希望能夠通過對(duì) channel 的關(guān)鍵特性的理解,進(jìn)一步掌握其用法細(xì)節(jié)以及 Golang 語言設(shè)計(jì)哲學(xué)的管窺蠡測(cè)。

channel 是可以讓一個(gè) goroutine 發(fā)送特定值到另一個(gè) gouroutine 的通信機(jī)制。

原生的 channel 是沒有緩存的(unbuffered channel),可以用于 goroutine 之間實(shí)現(xiàn)同步。

關(guān)閉后不能再寫入,可以讀取直到 channel 中再?zèng)]有數(shù)據(jù),并返回元素類型的零值。

gopl/ch3/netcat3

首先從 channel 是怎么被創(chuàng)建的開始:

在 heap 上分配一個(gè) hchan 類型的對(duì)象,并將其初始化,然后返回一個(gè)指向這個(gè) hchan 對(duì)象的指針。

理解了 channel 的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),現(xiàn)在轉(zhuǎn)到 channel 的兩個(gè)最基本方法: sends 和 receivces ,看一下以上的特性是如何體現(xiàn)在 sends 和 receives 中的:

假設(shè)發(fā)送方先啟動(dòng),執(zhí)行 ch - task0 :

如此為 channel 帶來了 goroutine-safe 的特性。

在這樣的模型里, sender goroutine - channel - receiver goroutine 之間, hchan 是唯一的共享內(nèi)存,而這個(gè)唯一的共享內(nèi)存又通過 mutex 來確保 goroutine-safe ,所有在隊(duì)列中的內(nèi)容都只是副本。

這便是著名的 golang 并發(fā)原則的體現(xiàn):

發(fā)送方 goroutine 會(huì)阻塞,暫停,并在收到 receive 后才恢復(fù)。

goroutine 是一種 用戶態(tài)線程 , 由 Go runtime 創(chuàng)建并管理,而不是操作系統(tǒng),比起操作系統(tǒng)線程來說,goroutine更加輕量。

Go runtime scheduler 負(fù)責(zé)將 goroutine 調(diào)度到操作系統(tǒng)線程上。

runtime scheduler 怎么將 goroutine 調(diào)度到操作系統(tǒng)線程上?

當(dāng)阻塞發(fā)生時(shí),一次 goroutine 上下文切換的全過程:

然而,被阻塞的 goroutine 怎么恢復(fù)過來?

阻塞發(fā)生時(shí),調(diào)用 runtime sheduler 執(zhí)行 gopark 之前,G1 會(huì)創(chuàng)建一個(gè) sudog ,并將它存放在 hchan 的 sendq 中。 sudog 中便記錄了即將被阻塞的 goroutine G1 ,以及它要發(fā)送的數(shù)據(jù)元素 task4 等等。

接收方 將通過這個(gè) sudog 來恢復(fù) G1

接收方 G2 接收數(shù)據(jù), 并發(fā)出一個(gè) receivce ,將 G1 置為 runnable :

同樣的, 接收方 G2 會(huì)被阻塞,G2 會(huì)創(chuàng)建 sudoq ,存放在 recvq ,基本過程和發(fā)送方阻塞一樣。

不同的是,發(fā)送方 G1如何恢復(fù)接收方 G2,這是一個(gè)非常神奇的實(shí)現(xiàn)。

理論上可以將 task 入隊(duì),然后恢復(fù) G2, 但恢復(fù) G2后,G2會(huì)做什么呢?

G2會(huì)將隊(duì)列中的 task 復(fù)制出來,放到自己的 memory 中,基于這個(gè)思路,G1在這個(gè)時(shí)候,直接將 task 寫到 G2的 stack memory 中!

這是違反常規(guī)的操作,理論上 goroutine 之間的 stack 是相互獨(dú)立的,只有在運(yùn)行時(shí)可以執(zhí)行這樣的操作。

這么做純粹是出于性能優(yōu)化的考慮,原來的步驟是:

優(yōu)化后,相當(dāng)于減少了 G2 獲取鎖并且執(zhí)行 memcopy 的性能消耗。

channel 設(shè)計(jì)背后的思想可以理解為 simplicity 和 performance 之間權(quán)衡抉擇,具體如下:

queue with a lock prefered to lock-free implementation:

比起完全 lock-free 的實(shí)現(xiàn),使用鎖的隊(duì)列實(shí)現(xiàn)更簡(jiǎn)單,容易實(shí)現(xiàn)


網(wǎng)頁標(biāo)題:go語言mutex的簡(jiǎn)單介紹
本文來源:http://weahome.cn/article/dseiipe.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部