淺析 go sync包

淺析 go sync包

背景介紹

盡管 Golang 推薦通過(guò) channel 進(jìn)行通信和同步枢贿,但在實(shí)際開(kāi)發(fā)中 sync 包用得也非常的多

var a = 0
// 啟動(dòng) 100 個(gè)協(xié)程,需要足夠大
// var lock sync.Mutex
for i := 0; i < 100; i++ {
    go func(idx int) {
        // lock.Lock()
        // defer lock.Unlock()
        a += 1
        fmt.Printf("goroutine %d, a=%d\n", idx, a)
    }(i)
}
// 等待 1s 結(jié)束主程序
// 確保所有協(xié)程執(zhí)行完
time.Sleep(time.Second)

互斥鎖sync.Mutex吊趾,讀寫(xiě)鎖sync.RWMutex

鎖的一些概念及使用方法,

整個(gè)包圍繞 Locker 進(jìn)行,這是一個(gè) interface:

type Locker interface {
        Lock()
        Unlock()
}

互斥鎖 Mutex

func (m *Mutex) Lock()
func (m *Mutex) Unlock()

使用須知:

  • 一個(gè)互斥鎖只能同時(shí)被一個(gè) goroutine 鎖定,其它 goroutine 將阻塞直到互斥鎖被解鎖(重新?tīng)?zhēng)搶對(duì)互斥鎖的鎖定)

  • 對(duì)一個(gè)未鎖定的互斥鎖解鎖將會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤烁涌。

讀寫(xiě)鎖 RWMutex

func (rw *RWMutex) Lock()       //寫(xiě)鎖定
func (rw *RWMutex) Unlock()     //寫(xiě)解鎖

func (rw *RWMutex) RLock()      //讀鎖定
func (rw *RWMutex) RUnlock()    //讀解鎖

使用須知:

  • 當(dāng)有一個(gè) goroutine 獲得寫(xiě)鎖定,其它無(wú)論是讀鎖定還是寫(xiě)鎖定都將阻塞直到寫(xiě)解鎖酒觅;
  • 當(dāng)有一個(gè) goroutine 獲得讀鎖定撮执,其它讀鎖定任然可以繼續(xù);
  • 當(dāng)有一個(gè)或任意多個(gè)讀鎖定舷丹,寫(xiě)鎖定將等待所有讀鎖定解鎖之后才能夠進(jìn)行寫(xiě)鎖定抒钱。所以說(shuō)這里的讀鎖定(RLock)目的其實(shí)是告訴寫(xiě)鎖定:有很多人正在讀取數(shù)據(jù),你給我站一邊去,等它們讀(讀解鎖)完你再來(lái)寫(xiě)(寫(xiě)鎖定)谋币。
var count int
var rw sync.RWMutex

func main() {
    ch := make(chan struct{}, 10)
    for i := 0; i < 5; i++ {
        go read(i, ch)
    }
    for i := 0; i < 5; i++ {
        go write(i, ch)
    }

    for i := 0; i < 10; i++ {
        <-ch
    }
}

func read(n int, ch chan struct{}) {
    rw.RLock()
    fmt.Printf("goroutine %d 進(jìn)入讀操作...\n", n)
    v := count
    fmt.Printf("goroutine %d 讀取結(jié)束仗扬,值為:%d\n", n, v)
    rw.RUnlock()
    ch <- struct{}{}
}

func write(n int, ch chan struct{}) {
    rw.Lock()
    fmt.Printf("goroutine %d 進(jìn)入寫(xiě)操作...\n", n)
    v := rand.Intn(1000)
    count = v
    fmt.Printf("goroutine %d 寫(xiě)入結(jié)束,新值為:%d\n", n, v)
    rw.Unlock()
    ch <- struct{}{}
}

sync.Waitgroup,sync.Once

WaitGroup

用于等待一組 goroutine 結(jié)束蕾额,用法很簡(jiǎn)單早芭。它有三個(gè)方法:

func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()

說(shuō)明: Add 用來(lái)添加 goroutine 的個(gè)數(shù)。Done 執(zhí)行一次數(shù)量減 1诅蝶。Wait 用來(lái)等待結(jié)束.

注意: wg.Add() 方法一定要在 goroutine 開(kāi)始前執(zhí)行哦退个。

var wg sync.WaitGroup

for i, s := range seconds {
    // 計(jì)數(shù)加 1
    wg.Add(1)
    go func(i, s int) {
        // 計(jì)數(shù)減 1
        defer wg.Done()
        fmt.Printf("goroutine%d 結(jié)束\n", i)
    }(i, s)
}

// 等待執(zhí)行結(jié)束
wg.Wait()
fmt.Println("所有 goroutine 執(zhí)行結(jié)束")

Once

func (o *Once) Do(f func())

使用 sync.Once 對(duì)象可以使得函數(shù)多次調(diào)用只執(zhí)行一次

var once sync.Once
onceBody := func() {
    fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
    go func() {
        once.Do(onceBody)
        done <- true
    }()
}
for i := 0; i < 10; i++ {
    <-done
}

----
# 打印結(jié)果
Only once

sync.Map

sync.Map是一個(gè)并發(fā)版本的Go語(yǔ)言的map

  • 使用Store(interface {},interface {})添加元素调炬。
  • 使用Load(interface {}) interface {}檢索元素语盈。
  • 使用Delete(interface {})刪除元素。
  • 使用LoadOrStore(interface {}缰泡,interface {}) (interface {}刀荒,bool)檢索或添加之前不存在的元素。如果鍵之前在map中存在棘钞,則返回的布爾值為true缠借。
  • 使用Range遍歷元素。
var m sync.Map
// m:=&sync.Map{}

// 添加元素
m.Store(1, "one")
m.Store(2, "two")

// 迭代所有元素
m.Range(func(key, value interface{}) bool {
    fmt.Printf("%d: %s\n", key.(int), value.(string))
    return true
})

// 獲取元素1
value, ok := m.Load(1)
fmt.Println(value,ok)   //one true

// 返回已存value武翎,否則把指定的鍵值存儲(chǔ)到map中
value, loaded := m.LoadOrStore(1, "three")
fmt.Println(value,loaded)   //one true

value1, loaded1 := m.LoadOrStore(3, "three")
fmt.Println(value1,loaded1) //three false

m.Delete(3)

sync.Pool

在 golang 中有一個(gè)池pool烈炭,目的:
復(fù)用已經(jīng)使用過(guò)的對(duì)象,來(lái)達(dá)到優(yōu)化內(nèi)存使用和回收的目的宝恶。
說(shuō)白了符隙,一開(kāi)始這個(gè)池子會(huì)初始化一些對(duì)象供你使用,如果不夠了呢垫毙,自己會(huì)通過(guò)new產(chǎn)生一些霹疫,當(dāng)你放回去了之后這些對(duì)象會(huì)被別人進(jìn)行復(fù)用,當(dāng)對(duì)象特別大并且使用非常頻繁的時(shí)候可以大大的減少對(duì)象的創(chuàng)建和回收的時(shí)間综芥。

簡(jiǎn)單案例

一共只有三個(gè)方法我們需要知道的:New丽蝎、Put、Get

var pool = sync.Pool{
    New: func() interface{} {
        return "123"
    },
}

func main() {
    t := pool.Get().(string)
    fmt.Println(t)

    pool.Put("321")
    
    t2 := pool.Get().(string)
    fmt.Println(t2)
}

---輸出:
123
321

源碼結(jié)構(gòu)分析

type Pool struct {
    noCopy noCopy

    local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
    localSize uintptr        // size of the local array

    victim     unsafe.Pointer // local from previous cycle
    victimSize uintptr        // size of victims array

    // New optionally specifies a function to generate
    // a value when Get would otherwise return nil.
    // It may not be changed concurrently with calls to Get.
    New func() interface{}
}

// Local per-P Pool appendix.
type poolLocalInternal struct {
    private interface{} // Can be used only by the respective P.
    shared  poolChain   // Local P can pushHead/popHead; any P can popTail.
}

type poolLocal struct {
    poolLocalInternal

    // Prevents false sharing on widespread platforms with
    // 128 mod (cache line size) = 0 .
    pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}

其實(shí)結(jié)構(gòu)并不復(fù)雜膀藐,但是如果自己看的話有點(diǎn)懵屠阻。注意幾個(gè)細(xì)節(jié)就ok。

  • local這里面真正的是[P]poolLocal其中P就是GPM模型中的P额各,有多少個(gè)P數(shù)組就有多大国觉,也就是每個(gè)P維護(hù)了一個(gè)本地的poolLocal。
  • poolLocal里面維護(hù)了一個(gè)private一個(gè)shared虾啦,看名字其實(shí)就很明顯了麻诀,private是給自己用的痕寓,而shared的是一個(gè)隊(duì)列,可以給別人用的蝇闭。注釋寫(xiě)的也很清楚呻率,自己可以從隊(duì)列的頭部存然后從頭部取,而別的P可以從尾部取呻引。
  • victim這個(gè)從字面上面也可以知道礼仗,幸存者嘛,當(dāng)進(jìn)行g(shù)c的stw時(shí)候逻悠,會(huì)將local中的對(duì)象移到victim中去藐守,也就是說(shuō)幸存了一次gc,

1. Get的邏輯其實(shí)非常清晰:

  • 如果 private 不是空的蹂风,那就直接拿來(lái)用
  • 如果 private 是空的,那就先去本地的shared隊(duì)列里面從頭 pop 一個(gè)
  • 如果本地的 shared 也沒(méi)有了乾蓬,那 getSlow 去拿惠啄,其實(shí)就是去別的P的 shared 里面偷,偷不到回去 victim 幸存者里面找
  • 如果最后都沒(méi)有任内,那就只能調(diào)用 New 方法創(chuàng)建一個(gè)了
image.png

2. Put邏輯就很簡(jiǎn)單了:

  • 如果 private 沒(méi)有撵渡,就放在 private
  • 如果 private 有了,那么就放到 shared 隊(duì)列的頭部

在看一個(gè)例子:

Put之后GC后Get

var pool = sync.Pool{
    New: func() interface{} {
        return "123"
    },
}

func main() {
    t := pool.Get().(string)
    fmt.Println(t)

    pool.Put("321")
    pool.Put("321")
    pool.Put("321")
    pool.Put("321")

    runtime.GC()
    time.Sleep(1 * time.Second)

    t2 := pool.Get().(string)
    fmt.Println(t2)

    runtime.GC()
    time.Sleep(1 * time.Second)

    t2 = pool.Get().(string)
    fmt.Println(t2)
}
---輸出:
123
321
123

思考:

  1. 什么情況下適合使用sync.Pool呢死嗦?
  2. sync.Pool的對(duì)象什么時(shí)候會(huì)被回收呢趋距?
  3. sync.Pool是如何實(shí)現(xiàn)線程安全的?

sync.Cond

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末越除,一起剝皮案震驚了整個(gè)濱河市节腐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摘盆,老刑警劉巖翼雀,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異孩擂,居然都是意外死亡狼渊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門类垦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)狈邑,“玉大人,你說(shuō)我怎么就攤上這事蚤认∶灼唬” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵烙懦,是天一觀的道長(zhǎng)驱入。 經(jīng)常有香客問(wèn)我赤炒,道長(zhǎng),這世上最難降的妖魔是什么亏较? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任莺褒,我火速辦了婚禮,結(jié)果婚禮上雪情,老公的妹妹穿的比我還像新娘遵岩。我一直安慰自己,他們只是感情好巡通,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布尘执。 她就那樣靜靜地躺著,像睡著了一般宴凉。 火紅的嫁衣襯著肌膚如雪誊锭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天弥锄,我揣著相機(jī)與錄音丧靡,去河邊找鬼。 笑死籽暇,一個(gè)胖子當(dāng)著我的面吹牛温治,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戒悠,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼熬荆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了绸狐?” 一聲冷哼從身側(cè)響起卤恳,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎六孵,沒(méi)想到半個(gè)月后纬黎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡劫窒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年本今,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片主巍。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冠息,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出孕索,到底是詐尸還是另有隱情逛艰,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布搞旭,位于F島的核電站散怖,受9級(jí)特大地震影響菇绵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜镇眷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一咬最、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧欠动,春花似錦永乌、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至人芽,卻和暖如春望几,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萤厅。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工橄妆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祈坠。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像矢劲,于是被迫代替她去往敵國(guó)和親赦拘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容