天天看點

sync

sync包有以下幾個内容:

(1)sync.Pool 臨時對象池

(2)sync.Mutex 互斥鎖

(3)sync.RWMutex 讀寫互斥鎖

(4)sync.WaitGroup 組等待

(5)sync.Cond 條件等待

(6)sync.Once 單次執行

一、臨時對象池

Pool可以用來存儲臨時對象,其實原理就是這個對象池指向對象變量,以防沒有變量指向對象時,被GC所回收。其目的時為了避免重複建立相同的對象造成GC的負擔,其中存放的臨時對象随時可能被GC回收掉(如果該對象不再被其它變量引用)。

從Pool中取出對象時,如果Pool中沒有對象,将回nil,但是如果給Pool。New字段指定一個函數的話,Pool将使用函數建立一個新對象傳回。

Pool可以安全的在多個協程中并行使用,但Pool并不适用于所有空閑對象,Pool應該用來管理并發的協程共享的臨時對象,而不應該管理短壽命對象中的臨時對象,因為這種情況下記憶體不能很好的配置設定,這些短壽命對象應該自己實作空閑清單。

type Pool struct {
        // 建立臨時對象的函數
        New func() interface{}
}

// 向臨時對象池中存入對象
func (p *Pool) Put() x interface{}

// 向臨時對象池中取出對象
func (p *Pool) Get() interface{}
           

案例:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var pool sync.Pool
    var val interface{}
    pool.Put("1")
    pool.Put(12)
    pool.Put(true)

    for {
        val = pool.Get()
        if val == nil {
            break
        }
        fmt.Println(val)
    }
}
           

二、互斥鎖

互斥鎖用來保證再任一時刻,隻能有一個協程通路某對象。Mutex的初始值為解鎖狀态,Mutex通常作為其它結構體的匿名字段使用,使該結構體具有Lock和Unlock方法。

Mutex可以安全的再多個協程中并行使用。

注意:如果對未加鎖的進行解鎖,則會引發panic。

加鎖,保證資料能夠正确:

package main

import (
    "fmt"
    "sync"
)

var num int
var mu sync.Mutex
var wg sync.WaitGroup

func main() {
    wg.Add(10000)
    for i := 0; i < 10000; i++ {
        go Add()
    }
    wg.Wait()
    fmt.Println(num)
}

func Add() {
    mu.Lock() // 加鎖
    defer func() {
        mu.Unlock() // 解鎖
        wg.Done()
    }()

    num++
}
           

并發沒有加鎖,如下代碼,當你執行下面代碼時,會發現結果不等于10000,其原因是因為出現這樣的情況,就是其中一些協程剛好讀取了num的值,此時該協程剛好時間片結束,被挂起,沒有完成加1。然後其他協程進行加1,然後當排程回到原來那個協程,num = 原來的那個num(而不是最新的num) + 1,導緻num資料出錯:

package main

import (
    "fmt"
    "sync"
)

var num int
var wg sync.WaitGroup

func main() {
    wg.Add(10000)
    for i := 0; i < 10000; i++ {
        go Add()
    }
    wg.Wait()
    fmt.Println(num)
}

func Add() {
    defer wg.Done()
    num++
}
           

三、讀寫互斥鎖

RWMutex比Mutex多了一個“寫鎖定” 和 “讀鎖定”,可以讓多個協程同時讀取某對象。RWMutex的初始值為解鎖狀态。RWMutex通常作為其它結構體的匿名字段使用。

RWMutex可以安全的在多個協程中并行使用。

// Lock 将 rw 設定為寫狀态,禁止其他協程讀取或寫入
func (rw *RWMutex) Lock()

// Unlock 解除 rw 的寫鎖定狀态,如果rw未被鎖定,則該操作會引發 panic。
func (rw *RWMutex) Unlock()

// RLock 将 rw 設定為鎖定狀态,禁止其他協程寫入,但可以讀取。
func (rw *RWMutex) RLock()

// Runlock 解除 rw 設定為讀鎖定狀态,如果rw未被鎖定,則該操作會引發 panic。
func (rw *RWMutex) RUnLock()

// RLocker 傳回一個互斥鎖,将 rw.RLock 和 rw.RUnlock 封裝成一個 Locker 接口。
func (rw *RWMutex) RLocker() Locker
           

四、組等待

WaitGroup 用于等待一組協程的結束。主協程在建立每個子協程的時候先調用Add增加等待計數,每個子例程在結束時調用 Done 減少例程計數。之後,主協程通過 Wait 方法開始等待,直到計數器歸零才繼續執行。

// 計數器增加 delta,delte可以時負數
func (wg *WaitGroup) Add(delta int)

// 計數器減少1,等價于Add(-1)
func (wg *WaitGroup) Done()

// 等待直到計數器歸零。如果計數器小于0,則該操作會引發 panic。
func (wg *WaitGroup) Wait()
           

五、條件等待

條件等待和互斥鎖有不同,互斥鎖是不同協程公用一個鎖,條件等待是不同協程各用一個鎖,但是wait()方法調用會等待(阻塞),直到有信号發過來,不同協程是共用信号

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup
    cond := sync.NewCond(new(sync.Mutex))

    for i := 0; i < 3; i++ {
        go func(i int) {
            fmt.Println("協程", i, "啟動。。。")
            wg.Add(1)
            defer wg.Done()
            cond.L.Lock()
            fmt.Println("協程", i, "加鎖。。。")
            cond.Wait()
            fmt.Println("協程", i, "解鎖。。。")
            cond.L.Unlock()
        }(i)
    }
    time.Sleep(2e9)
    cond.L.Lock()
    fmt.Println("主協程發送信号量。。。")
    cond.Signal()
    cond.L.Unlock()

    time.Sleep(2e9)
    cond.L.Lock()
    fmt.Println("主協程發送信号量。。。")
    cond.Signal()
    cond.L.Unlock()

    time.Sleep(2e9)
    cond.L.Lock()
    fmt.Println("主協程發送信号量。。。")
    cond.Signal()
    cond.L.Unlock()
    wg.Wait()
}
           

六、單次執行

Once的作用是多次調用但隻執行一次,Once隻有一個方法,Once.Do(),向Do傳入一個函數,這個函數在第一次執行Once.Do()的時候會被調用,以後再執行Once.Do()将沒有任何動作,即使傳入了其他的函數,也不會被執行,如果要執行其它函數,需要重新建立一個Once對象。

Once可以安全的再多個協程中并行使用。是協程安全的

标準庫中原型:
// 多次調用僅執行一次指定的函數f
func (o *Once) Do(f func())

// 示例:Once
package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    var wg sync.WaitGroup

    onceFunc := func() {
        fmt.Println("hello")
    }
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go func() {
            defer wg.Done()
            once.Do(onceFunc) // 多次調用隻執行一次
        }()
    }
    wg.Wait()
}
           

參考:

(1)

https://www.cnblogs.com/golove/p/5918082.html

(2)

https://blog.csdn.net/wangshubo1989/article/details/77966432?locationNum=9&fps=1