go常用包——sync

內容

  • Atomic
  • Mutex
  • RWMutex
  • WaitGroup
  • Once
  • Sync.Map
  • Sync.Pool
  • Cond

Atomic

go中atomic 包實現(xiàn)原子操作

  • 基本類型的原子操作有6種:int32, int64, uint32, uint64, uintptr, unsafe.Pinter;操作類型有5種:增減, 比較并交換, 載入, 存儲,交換
  • Value提供任意類型的原子操作,操作類型有:載入和存儲
基本數(shù)據(jù)類型:
    //增減操作
    var a int32;
    //增操作
    new_a := atomic.AddInt32(&a, 1);

    //減操作
    new_a = atomic.AddInt32(&a, -1);
 
    //CAS(Compare And Swap)比較并交換操作
    //函數(shù)名以CompareAndSwap為前綴,并具體類型名
    //函數(shù)會先判斷參數(shù)一指向的值與參數(shù)二是否相等,如果相等,則用參數(shù)三替換參數(shù)一的值。
    var b int32;
    atomic.CompareAndSwapInt32(&b, 0, 3);
 
    //載入操作
    //當我們對某個變量進行讀取操作時,可能該變量正在被其他操作改變,或許我們讀取的是被修改了一半的數(shù)據(jù)。
    //所以我們通過Load這類函數(shù)來確保我們正確的讀取
    //函數(shù)名以Load為前綴,加具體類型名
    var c int32;
    tmp := atomic.LoadInt32(&c);

    //存儲操作
    //與載入函數(shù)相對應,提供原子的存儲函數(shù); 函數(shù)名以Store為前綴,加具體類型名
    var d int32;
    //存儲某個值時,任何CPU都不會都該值進行讀或寫操作
    //存儲操作總會成功,它不關心舊值是什么,與CAS不同
    atomic.StoreInt32(&d, 666);

任意類型:
提供了兩個方法,保證原子的存儲和讀?。úl(fā)操作時,保證不會讀到正在寫入到一半的值)
1 func (v *Value) Load() (x interface{})
2 func (v *Value) Store(x interface{})

    type AtomicStruct struct {
        Age int
        name string
    }
    config := atomic.Value{} // 1 聲明一個value類型變量,相當于一個容器
    config.Store(&AtomicStruct{})  // 2 說明這個容器存儲的是AtomicStruct 指針類型,一旦說明后,后續(xù)的store操作只能是這個類型

    wg := sync.WaitGroup{}
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go func(i int) {
            defer wg.Done()
            if i == 5 {
                name := "name" + string(i)
                config.Store(&AtomicStruct{Age: i, name: name})
            }
            fmt.Println(config.Load())
        }(i)
    }
    wg.Wait()

Mutex

互斥鎖: go中通過sync.Mutex的零值來表示一個沒有被鎖定的互斥量,開箱即用,申明一個變量就可以用,但是如果把互斥量當做入?yún)魅氲胶瘮?shù)中使用時,只能傳遞指針,不能是值傳遞,值傳遞是拷貝,不能達到鎖住的作用。

var x int 
func main()  {
var m sync.Mutex
add(&m, 2)
}

// 同步的做甲方
func add(m *sync.Mutex, value int){
m.Lock()
x = x + value
m.Unlock()
}

RWMutex

讀寫鎖: go中通過sync.RWMutex的零值來表示一個讀寫鎖實例,讀寫鎖中有兩對方法,分別是讀鎖定和讀解鎖、寫鎖定和寫解鎖

  • 規(guī)則:讀寫鎖下,多個寫操作之間是互斥的,寫操作和讀操作之間也是互斥的,但是多個讀操作之間不存在互斥關系
rw.Lock() // 寫鎖定
rw.Unlock()
rw.RLock() // 讀鎖定
rw.RUnlock()

func main()  {
   var rw sync.RWMutex
   go func() {
      rw.RLock()
      println(1)
      time.Sleep(2*time.Second)
      rw.RUnlock()
      println(2)
   }()
   time.Sleep(1*time.Second)
   println(3)
    // 被堵在這里,因為協(xié)程中 rw.RLock()加了讀鎖,會阻塞寫鎖,不會堵塞其他協(xié)程獲取讀鎖,rw.RUnlock()執(zhí)行后,釋放了讀鎖,同樣也會釋放寫鎖,前提是已經(jīng)沒有任何其他的讀鎖了;如果是先執(zhí)行rw.Lock(),同樣也會堵塞其他寫鎖和讀鎖,rw.Ulock()會把讀鎖和寫鎖全部釋放
   rw.Lock()
   println(4)
}

WaitGroup

  • wg會阻塞在wait()方法上,等待一組協(xié)程執(zhí)行結束,同樣是開箱即用,在函數(shù)中當做入?yún)鬟f時,記住傳遞指針
func main()  {
   var wg sync.WaitGroup
   wg.Add(1)
   go func() {
      println(1)
      wg.Done()
   }()

   //主協(xié)程阻塞在這里,必定會等協(xié)程中執(zhí)行完畢后,才會執(zhí)行打印2
   wg.Wait()
   println(2)
}

Once

  • sync.Once 同樣開箱即用,申明即可, Do方法接收一個無參數(shù)、無結果的函數(shù)作為參數(shù),保證這個方法只執(zhí)行一次
  • 原理是Once結構體內維護了一個無符號的變量done,當執(zhí)行一次func后會原子的加1,如果done不會0時,就不會執(zhí)行func了
 var once sync.Once //同樣開箱即用
// Do方法接收一個無參數(shù)、無結果的函數(shù)作為參數(shù),保證這個方法只執(zhí)行一次
 Once.Do(func(){
    fmt.println(“func方法只執(zhí)行一次")
})

// 原理是Once結構體內維護了一個無符號的變量done,當執(zhí)行一次func后會原子的加1,如果done不會0時,就不會執(zhí)行func了

func (o *Once) Do(f func()) {
   if atomic.LoadUint32(&o.done) == 0 {
      // Outlined slow-path to allow inlining of the fast-path.
      o.doSlow(f)
   }
}
func (o *Once) doSlow(f func()) {
   o.m.Lock()
   defer o.m.Unlock()
   if o.done == 0 {
      defer atomic.StoreUint32(&o.done, 1)
      f()
   }
}

sync.Map

  • 并發(fā)安全的map
func main(){
    var sm sync.Map
    // 寫入
    sm.Store("a", 1)
    sm.Store("b", 2)
    sm.Store("c", 3)

    // 取出
    v, _ := sm.Load("a")
    fmt.Println(v.(int))

    // 刪除
    sm.Delete("b")

    // 存在則獲取 不存在則添加
    sm.LoadOrStore("d", 4)

    // 遍歷
    sm.Range(func(key, value interface{}) bool {
        fmt.Println(key.(string), value.(int))
        return true
    })
}

Sync.Pool

  • Pool用來做一個臨時對象池,當某個對象會被經(jīng)常創(chuàng)建或是在并發(fā)場景下多個協(xié)程會會創(chuàng)建相同的對象,此時可以考慮用sync.Pool來優(yōu)化性能,避免大量對象的創(chuàng)建 銷毀引起的GC問題。
  • Pool只有兩個方法,Get和Put, Get從對象池中獲取對象,如果不存在則用初始化對象池是賦予的New方法創(chuàng)建一個對象返回, Put方法用于當用完對象后,把對象歸還到對象池,如果對象需要保持初始化狀態(tài),則用完對象后,應該對對象做一些清零的邏輯,然后在歸還到池子中,否則下一次get是,獲取到的對象會保存了上一次put的記錄;
  • Pool不適于做連接池之類的,因為pool會在GC是被回收的;
  • Pool是并發(fā)安全的;
    舉個例子
func SyncPoolExample()  {
    pool := sync.Pool{
        New: func() interface{} {
            fmt.Println("aa")
            return People{}
        },
    }
    for i:=0; i < 3; i++ {
        go func() {
            people := pool.Get().(People)
            fmt.Println(people)
            people.name = "li"
            fmt.Println(people)
            people.name = ""
            pool.Put(people)
        }()
        time.Sleep(time.Second * 2)
    }
    time.Sleep(time.Second * 10)
}

aa
{0 }
{0 li}
aa
{0 }
{0 li}
{0 }
{0 li}
當協(xié)程還沒有歸還對象到池子里時,如果其他協(xié)程此時來get,則就會新建一個

//example 2
func main() {
    pool := sync.Pool{
        New: func() interface{} {
            b := make([]byte, 0, 1024)
            return b
        },
    }
    b := pool.Get().([]byte)
    b = append(b, 0xff)

    b = b[:0] // 用完后,清空[]byte,在歸還池子
    pool.Put(b)
}

實現(xiàn)原理:

Cond

條件變量,類似java object中的wait notify和notifyAll方法,Cond也提供了三個方法Wait、Singal、Broadcast

func SyncCondExample(){
    cond := sync.NewCond(new(sync.Mutex))
    for i := 0; i < 10; i++ {
        go func(i int) {
            cond.L.Lock()
            cond.Wait()
            fmt.Println("goroutine: ", i)
            cond.L.Unlock()
        }(i)
    }
    fmt.Println("all goroutines wait...")
    time.Sleep(time.Second * 3)
    //  按等待順序 釋放一個
    //cond.Signal()
    // 釋放所有
    cond.Broadcast()
    time.Sleep(time.Second * 3)
}

all goroutines wait...
goroutine:  4
goroutine:  8
goroutine:  5
goroutine:  1
goroutine:  0
goroutine:  7
goroutine:  3
goroutine:  2
goroutine:  6
goroutine:  9
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容