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

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

go語(yǔ)言全局變量加鎖 go語(yǔ)言鎖機(jī)制

(十一)golang 內(nèi)存分析

編寫過(guò)C語(yǔ)言程序的肯定知道通過(guò)malloc()方法動(dòng)態(tài)申請(qǐng)內(nèi)存,其中內(nèi)存分配器使用的是glibc提供的ptmalloc2。 除了glibc,業(yè)界比較出名的內(nèi)存分配器有Google的tcmalloc和Facebook的jemalloc。二者在避免內(nèi)存碎片和性能上均比glic有比較大的優(yōu)勢(shì),在多線程環(huán)境中效果更明顯。

創(chuàng)新互聯(lián)公司堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的三門峽網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!

Golang中也實(shí)現(xiàn)了內(nèi)存分配器,原理與tcmalloc類似,簡(jiǎn)單的說(shuō)就是維護(hù)一塊大的全局內(nèi)存,每個(gè)線程(Golang中為P)維護(hù)一塊小的私有內(nèi)存,私有內(nèi)存不足再?gòu)娜稚暾?qǐng)。另外,內(nèi)存分配與GC(垃圾回收)關(guān)系密切,所以了解GC前有必要了解內(nèi)存分配的原理。

為了方便自主管理內(nèi)存,做法便是先向系統(tǒng)申請(qǐng)一塊內(nèi)存,然后將內(nèi)存切割成小塊,通過(guò)一定的內(nèi)存分配算法管理內(nèi)存。 以64位系統(tǒng)為例,Golang程序啟動(dòng)時(shí)會(huì)向系統(tǒng)申請(qǐng)的內(nèi)存如下圖所示:

預(yù)申請(qǐng)的內(nèi)存劃分為spans、bitmap、arena三部分。其中arena即為所謂的堆區(qū),應(yīng)用中需要的內(nèi)存從這里分配。其中spans和bitmap是為了管理arena區(qū)而存在的。

arena的大小為512G,為了方便管理把a(bǔ)rena區(qū)域劃分成一個(gè)個(gè)的page,每個(gè)page為8KB,一共有512GB/8KB個(gè)頁(yè);

spans區(qū)域存放span的指針,每個(gè)指針對(duì)應(yīng)一個(gè)page,所以span區(qū)域的大小為(512GB/8KB)乘以指針大小8byte = 512M

bitmap區(qū)域大小也是通過(guò)arena計(jì)算出來(lái),不過(guò)主要用于GC。

span是用于管理arena頁(yè)的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),每個(gè)span中包含1個(gè)或多個(gè)連續(xù)頁(yè),為了滿足小對(duì)象分配,span中的一頁(yè)會(huì)劃分更小的粒度,而對(duì)于大對(duì)象比如超過(guò)頁(yè)大小,則通過(guò)多頁(yè)實(shí)現(xiàn)。

根據(jù)對(duì)象大小,劃分了一系列class,每個(gè)class都代表一個(gè)固定大小的對(duì)象,以及每個(gè)span的大小。如下表所示:

上表中每列含義如下:

class: class ID,每個(gè)span結(jié)構(gòu)中都有一個(gè)class ID, 表示該span可處理的對(duì)象類型

bytes/obj:該class代表對(duì)象的字節(jié)數(shù)

bytes/span:每個(gè)span占用堆的字節(jié)數(shù),也即頁(yè)數(shù)乘以頁(yè)大小

objects: 每個(gè)span可分配的對(duì)象個(gè)數(shù),也即(bytes/spans)/(bytes/obj)waste

bytes: 每個(gè)span產(chǎn)生的內(nèi)存碎片,也即(bytes/spans)%(bytes/obj)上表可見最大的對(duì)象是32K大小,超過(guò)32K大小的由特殊的class表示,該class ID為0,每個(gè)class只包含一個(gè)對(duì)象。

span是內(nèi)存管理的基本單位,每個(gè)span用于管理特定的class對(duì)象, 跟據(jù)對(duì)象大小,span將一個(gè)或多個(gè)頁(yè)拆分成多個(gè)塊進(jìn)行管理。src/runtime/mheap.go:mspan定義了其數(shù)據(jù)結(jié)構(gòu):

以class 10為例,span和管理的內(nèi)存如下圖所示:

spanclass為10,參照class表可得出npages=1,nelems=56,elemsize為144。其中startAddr是在span初始化時(shí)就指定了某個(gè)頁(yè)的地址。allocBits指向一個(gè)位圖,每位代表一個(gè)塊是否被分配,本例中有兩個(gè)塊已經(jīng)被分配,其allocCount也為2。next和prev用于將多個(gè)span鏈接起來(lái),這有利于管理多個(gè)span,接下來(lái)會(huì)進(jìn)行說(shuō)明。

有了管理內(nèi)存的基本單位span,還要有個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)管理span,這個(gè)數(shù)據(jù)結(jié)構(gòu)叫mcentral,各線程需要內(nèi)存時(shí)從mcentral管理的span中申請(qǐng)內(nèi)存,為了避免多線程申請(qǐng)內(nèi)存時(shí)不斷的加鎖,Golang為每個(gè)線程分配了span的緩存,這個(gè)緩存即是cache。src/runtime/mcache.go:mcache定義了cache的數(shù)據(jù)結(jié)構(gòu)

alloc為mspan的指針數(shù)組,數(shù)組大小為class總數(shù)的2倍。數(shù)組中每個(gè)元素代表了一種class類型的span列表,每種class類型都有兩組span列表,第一組列表中所表示的對(duì)象中包含了指針,第二組列表中所表示的對(duì)象不含有指針,這么做是為了提高GC掃描性能,對(duì)于不包含指針的span列表,沒必要去掃描。根據(jù)對(duì)象是否包含指針,將對(duì)象分為noscan和scan兩類,其中noscan代表沒有指針,而scan則代表有指針,需要GC進(jìn)行掃描。mcache和span的對(duì)應(yīng)關(guān)系如下圖所示:

mchache在初始化時(shí)是沒有任何span的,在使用過(guò)程中會(huì)動(dòng)態(tài)的從central中獲取并緩存下來(lái),跟據(jù)使用情況,每種class的span個(gè)數(shù)也不相同。上圖所示,class 0的span數(shù)比class1的要多,說(shuō)明本線程中分配的小對(duì)象要多一些。

cache作為線程的私有資源為單個(gè)線程服務(wù),而central則是全局資源,為多個(gè)線程服務(wù),當(dāng)某個(gè)線程內(nèi)存不足時(shí)會(huì)向central申請(qǐng),當(dāng)某個(gè)線程釋放內(nèi)存時(shí)又會(huì)回收進(jìn)central。src/runtime/mcentral.go:mcentral定義了central數(shù)據(jù)結(jié)構(gòu):

lock: 線程間互斥鎖,防止多線程讀寫沖突

spanclass : 每個(gè)mcentral管理著一組有相同class的span列表

nonempty: 指還有內(nèi)存可用的span列表

empty: 指沒有內(nèi)存可用的span列表

nmalloc: 指累計(jì)分配的對(duì)象個(gè)數(shù)線程從central獲取span步驟如下:

將span歸還步驟如下:

從mcentral數(shù)據(jù)結(jié)構(gòu)可見,每個(gè)mcentral對(duì)象只管理特定的class規(guī)格的span。事實(shí)上每種class都會(huì)對(duì)應(yīng)一個(gè)mcentral,這個(gè)mcentral的集合存放于mheap數(shù)據(jù)結(jié)構(gòu)中。src/runtime/mheap.go:mheap定義了heap的數(shù)據(jù)結(jié)構(gòu):

lock: 互斥鎖

spans: 指向spans區(qū)域,用于映射span和page的關(guān)系

bitmap:bitmap的起始地址

arena_start: arena區(qū)域首地址

arena_used: 當(dāng)前arena已使用區(qū)域的最大地址

central: 每種class對(duì)應(yīng)的兩個(gè)mcentral

從數(shù)據(jù)結(jié)構(gòu)可見,mheap管理著全部的內(nèi)存,事實(shí)上Golang就是通過(guò)一個(gè)mheap類型的全局變量進(jìn)行內(nèi)存管理的。mheap內(nèi)存管理示意圖如下:

系統(tǒng)預(yù)分配的內(nèi)存分為spans、bitmap、arean三個(gè)區(qū)域,通過(guò)mheap管理起來(lái)。接下來(lái)看內(nèi)存分配過(guò)程。

針對(duì)待分配對(duì)象的大小不同有不同的分配邏輯:

(0, 16B) 且不包含指針的對(duì)象: Tiny分配

(0, 16B) 包含指針的對(duì)象:正常分配

[16B, 32KB] : 正常分配

(32KB, -) : 大對(duì)象分配其中Tiny分配和大對(duì)象分配都屬于內(nèi)存管理的優(yōu)化范疇,這里暫時(shí)僅關(guān)注一般的分配方法。

以申請(qǐng)size為n的內(nèi)存為例,分配步驟如下:

Golang內(nèi)存分配是個(gè)相當(dāng)復(fù)雜的過(guò)程,其中還摻雜了GC的處理,這里僅僅對(duì)其關(guān)鍵數(shù)據(jù)結(jié)構(gòu)進(jìn)行了說(shuō)明,了解其原理而又不至于深陷實(shí)現(xiàn)細(xì)節(jié)。1、Golang程序啟動(dòng)時(shí)申請(qǐng)一大塊內(nèi)存并劃分成spans、bitmap、arena區(qū)域

2、arena區(qū)域按頁(yè)劃分成一個(gè)個(gè)小塊。

3、span管理一個(gè)或多個(gè)頁(yè)。

4、mcentral管理多個(gè)span供線程申請(qǐng)使用

5、mcache作為線程私有資源,資源來(lái)源于mcentral。

go語(yǔ)言的map多協(xié)程訪問(wèn)時(shí)需要加鎖嗎

go語(yǔ)言的map多協(xié)程訪問(wèn)時(shí)需要加鎖

支持==和!=操作就可以做key,實(shí)際上只有function、map、slice三個(gè)kind不支持作為key,因?yàn)橹荒芎蚽il比較不能和另一個(gè)值比較。布爾、整型、浮點(diǎn)、復(fù)數(shù)、字符串、指針、channel等都可以做key。

struct能不能做key要看每一個(gè)字段,如果所有字段都可以做key,那這個(gè)struct就可以。有一個(gè)字段不能做key,這個(gè)struct就不能做key。array也是,元素類型能做key,那這個(gè)array就可以。

例如:

type Foo map[struct {

B bool

I int

F float64

C complex128

S string

P *Foo

Ch chan Foo

}]bool

每一個(gè)字段都可以做key,F(xiàn)oo就可以做key。再如:

type Foo map[struct {

Fn func() Foo

M map[*Foo]int

S []Foo

}]bool

有一個(gè)字段不能做key、Foo就不允許做key,而這三個(gè)字段都不能。

字段是遞歸檢查的:

type Foo map[struct {

Sub struct {

M map[*Foo]bool

}

}]bool

Sub的M字段不能做key,Sub就不能做key,F(xiàn)oo也就不能做key。

總之想把一個(gè)數(shù)據(jù)結(jié)構(gòu)用于map的key,就不能包含function、map和slice。

Go語(yǔ)言——sync.Map詳解

sync.Map是1.9才推薦的并發(fā)安全的map,除了互斥量以外,還運(yùn)用了原子操作,所以在這之前,有必要了解下 Go語(yǔ)言——原子操作

go1.10\src\sync\map.go

entry分為三種情況:

從read中讀取key,如果key存在就tryStore。

注意這里開始需要加鎖,因?yàn)樾枰僮鱠irty。

條目在read中,首先取消標(biāo)記,然后將條目保存到dirty里。(因?yàn)闃?biāo)記的數(shù)據(jù)不在dirty里)

最后原子保存value到條目里面,這里注意read和dirty都有條目。

總結(jié)一下Store:

這里可以看到dirty保存了數(shù)據(jù)的修改,除非可以直接原子更新read,繼續(xù)保持read clean。

有了之前的經(jīng)驗(yàn),可以猜測(cè)下load流程:

與猜測(cè)的 區(qū)別 :

由于數(shù)據(jù)保存兩份,所以刪除考慮:

先看第二種情況。加鎖直接刪除dirty數(shù)據(jù)。思考下貌似沒什么問(wèn)題,本身就是臟數(shù)據(jù)。

第一種和第三種情況唯一的區(qū)別就是條目是否被標(biāo)記。標(biāo)記代表刪除,所以直接返回。否則CAS操作置為nil。這里總感覺少點(diǎn)什么,因?yàn)闂l目其實(shí)還是存在的,雖然指針nil。

看了一圈貌似沒找到標(biāo)記的邏輯,因?yàn)閯h除只是將他變成nil。

之前以為這個(gè)邏輯就是簡(jiǎn)單的將為標(biāo)記的條目拷貝給dirty,現(xiàn)在看來(lái)大有文章。

p == nil,說(shuō)明條目已經(jīng)被delete了,CAS將他置為標(biāo)記刪除。然后這個(gè)條目就不會(huì)保存在dirty里面。

這里其實(shí)就跟miss邏輯串起來(lái)了,因?yàn)閙iss達(dá)到閾值之后,dirty會(huì)全量變成read,也就是說(shuō)標(biāo)記刪除在這一步最終刪除。這個(gè)還是很巧妙的。

真正的刪除邏輯:

很繞。。。。

兩個(gè)線程加鎖累加全局變量,全局變量的值一定正確嗎

兩個(gè)線程加鎖累加全局變量,全局變量的值一定正確。當(dāng)VALUE的數(shù)據(jù)很大時(shí),兩個(gè)線程同時(shí)執(zhí)行的概率就很大,導(dǎo)致計(jì)算不準(zhǔn)確,以至于產(chǎn)生臟數(shù)據(jù),所以對(duì)數(shù)據(jù)加鎖是必要的。

go語(yǔ)言中全局變量和局部變量的區(qū)別

局部變量

在函數(shù)體內(nèi)聲明的變量稱之為局部變量,它們的作用域只在函數(shù)體內(nèi),參數(shù)和返回值變量也是局部變量。

以下實(shí)例中 main() 函數(shù)使用了局部變量 a, b, c:

package main

import "fmt"

func main() {

/* 聲明局部變量 */

var a, b, c int

/* 初始化參數(shù) */

a = 10

b = 20

c = a + b

fmt.Printf ("結(jié)果: a = %d, b = %d and c = %d\n", a, b, c)

}

以上實(shí)例執(zhí)行輸出結(jié)果為:

結(jié)果: a = 10, b = 20 and c = 30

全局變量

在函數(shù)體外聲明的變量稱之為全局變量,全局變量可以在整個(gè)包甚至外部包(被導(dǎo)出后)使用。

全局變量可以在任何函數(shù)中使用,以下實(shí)例演示了如何使用全局變量:

package main

import "fmt"

/* 聲明全局變量 */

var g int

func main() {

/* 聲明局部變量 */

var a, b int

/* 初始化參數(shù) */

a = 10

b = 20

g = a + b

fmt.Printf("結(jié)果: a = %d, b = %d and g = %d\n", a, b, g)

}

以上實(shí)例執(zhí)行輸出結(jié)果為:

結(jié)果: a = 10, b = 20 and g = 30

Go 語(yǔ)言程序中全局變量與局部變量名稱可以相同,但是函數(shù)內(nèi)的局部變量會(huì)被優(yōu)先考慮。實(shí)例如下:

package main

import "fmt"

/* 聲明全局變量 */

var g int = 20

func main() {

/* 聲明局部變量 */

var g int = 10

fmt.Printf ("結(jié)果: g = %d\n", g)

}

以上實(shí)例執(zhí)行輸出結(jié)果為:

結(jié)果: g = 10

多線程讀一個(gè)全局變量要不要加鎖?還是說(shuō)只是當(dāng)修改全局變量的時(shí)候才要加鎖?

如果所有線程都只讀取該變量的話不必加鎖,因?yàn)閮H讀取不存在破壞數(shù)據(jù)的風(fēng)險(xiǎn),如果有線程寫該變量的話不管讀取還是寫入都要加鎖的。

windowsAPI提供了一種Sim讀寫鎖,允許所有讀線程在同一時(shí)刻訪問(wèn)該資源,而寫線程在寫入時(shí)獨(dú)占資源。


本文名稱:go語(yǔ)言全局變量加鎖 go語(yǔ)言鎖機(jī)制
鏈接地址:http://weahome.cn/article/dodegss.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部