go的*鎖*包——sync

go的包——sync

上一篇文章介紹了各種鎖的基本概念,這篇文章主要學習一下Go的標準庫sync包,提供了基本的同步基元.

要注意的是,sync除了Once和WaitGroup類型,大部分都是適用於低水平程序線程,高水平的同步使用channel通信更好一些

互斥鎖——Mutex

  1. 互斥鎖: 同一時刻只能有一個讀或寫的場景

    var mu sync.Mutex
    
    func main()  {
      mu.Lock()
      // 使用defer釋放鎖的話會比顯示調用Unlock成本高
      defer mu.Unlock()
    }
    
  2. Mutex實現

    // A Mutex is a mutual exclusion lock.
    // The zero value for a Mutex is an unlocked mutex.
    //
    // A Mutex must not be copied after first use.
    type Mutex struct {
    	state int32
    	sema  uint32
    }
    
  3. 一個互斥鎖只能同時被一個goroutine鎖定,其他go程將被阻塞直到互斥鎖被解鎖,重新爭奪互斥鎖

  4. 要注意的是: Go中沒有重入鎖, 對一個已經上鎖的Mutex再次上鎖會導致程序死鎖

讀寫鎖——RWMutes

  1. 讀寫鎖/共享鎖: 允許有多個讀鎖,但只能有一個寫鎖,讀寫鎖可以分別對讀,寫進行鎖定,一般用於“多讀少寫”場景

    var rw sync.RWMutex
    
    func main()  {
    	rw.Lock()			// 對寫操作進行鎖定
    	rw.Unlock()		// 對寫操作進行解鎖
    	rw.RLock()		// 對讀操作進行鎖定
    	rw.RUnlock()	// 對讀操作進行解鎖
    }
    
  2. 寫鎖權限高於讀鎖, 寫鎖會優先鎖定,當有寫鎖時沒辦法獲得讀鎖, 當只有讀鎖或無鎖時可以獲取多個讀鎖

  3. 源碼解析可以看這篇文章:Golang 讀寫鎖RWMutex

單次執行——Once

  1. 使用once可以保證只運行一次,是協程安全的

    var once sync.Once
    func main(){
     	once.Do(
        func(){
        }
    	) 
    }
    
  2. Once的實現分析:

    • Once結構

      // Once is an object that will perform exactly one action.
      type Once struct {
      	// done indicates whether the action has been performed.
      	// It is first in the struct because it is used in the hot path.
      	// The hot path is inlined at every call site.
      	// Placing done first allows more compact instructions on some architectures (amd64/x86),
      	// and fewer instructions (to calculate offset) on other architectures.
      	done uint32
      	m    Mutex
      }
      
      • Done : 計數器,統計執行次數
      • m: 互斥鎖
    • Do

      func (o *Once) Do(f func()) {
      // Note: Here is an incorrect implementation of Do:
      //
      //	if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
      //		f()
      //	}
      //
      // Do guarantees that when it returns, f has finished.
      // This implementation would not implement that guarantee:
      // given two simultaneous calls, the winner of the cas would
      // call f, and the second would return immediately, without
      // waiting for the first's call to f to complete.
      // This is why the slow path falls back to a mutex, and why
      // the atomic.StoreUint32 must be delayed until after f returns.
      
      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()
      }
      }
      
      • 當done的次數爲0的時候執行doSlow
      • 執行後defer將done的值置爲1
      • 使用原子操作避免加鎖提高了性能
    • Once實現單例模式

      package singleton
      
      import (
      	"fmt"
      	"sync"
      )
      
      // 全局實例
      type singleton struct {
      	data int
      }
      
      var sing *singleton	// 小寫私有實例變量
      
      var once sync.Once	// 保證線程安全,只執行一次
      
      func GetInstance(num int) *singleton {
      	once.Do(func() {
      		sing = &singleton{data:num}
      		fmt.Println("實例對象的值爲和地址爲:", sing, &sing)
      	})
      	return sing
      }
      

等待組——WaitGroup

  1. 用於等待一組協程的結束,可以優雅的避免用sleep或for等待.

    // 1.創建子協程先調用Add增加等待計數
    // 2.子協程結束後調用Done減少協程計數
    // 3.主協程中調用Wait方法進行等待,直到計數器歸零繼續執行
    package main
    
    import (
    	"fmt"
    	"sync"
    	"time"
    )
    
    func main() {
    	var wg sync.WaitGroup
    
    	wg.Add(1)
    	go func() {
    		time.Sleep(time.Second * 3)
    		// 子協程結束了等待組就調用done
    		wg.Done()
    		fmt.Println("子協程1結束了")
    	}()
    
    	wg.Wait()
    	fmt.Println("main over")
    }
    
  2. WaitGroup源碼分析

條件等待——Cond

  1. 條件等待是不同協程各用一個鎖, 互斥鎖是不同協程公用一個鎖

  2. 條件等待不是像互斥鎖那樣來保護臨界區和共享資源的,是用於協調想要訪問共享資源的那些線程,維護一個等待隊列,在共享資源的狀態發生變化時,可以用來通知被互斥鎖所阻塞的線程

    package main
    
    import (
    	"fmt"
    	"sync"
    	"time"
    )
    
    /**
     * 條件等待
     */
    
    func main() {
    	var wg sync.WaitGroup
    	cond := sync.NewCond(&sync.Mutex{})
    	for i := 0; i < 10; i++ {
    		wg.Add(1)
    		go func(i int) {
    			cond.L.Lock()
    			defer cond.L.Unlock()
          // Wait()等待通知: 阻塞當前線程,直到收到該條件變量發來的通知
    			cond.Wait()		
    			wg.Done()
          // do other
    			fmt.Println(i)
    		}(i)
    	}
    
    	fmt.Println("正被阻塞。。。")
    
    	time.Sleep(time.Second * 1)
    
    	// Signal()單發通知: 讓該條件變量向至少一個正在等待它的通知的線程發送通知,表示共享數據的狀態已經改變。
    	cond.Signal()
    
    	fmt.Println("通知已被釋放")
    
    	time.Sleep(time.Second * 1)
    
    	fmt.Println("廣播")
    
    	// Broadcast廣播通知: 讓條件變量給正在等待它的通知的所有線程都發送通知。
    	cond.Broadcast()
    
    	wg.Wait()
    
    }
    
    // 正被阻塞。。。
    // 通知已被釋放
    // 4
    // 廣播
    // 9
    // 3
    // 0
    // 1
    // 2
    // 5
    // 8
    // 7
    // 6
    

協程安全的Map——Sync.Map

  1. 協程安全的map的特性:

    • Sync.Map無須初始化,直接聲明即可, 不能使用make創建
    • Sync.Map 不能使用 map 的方式進行取值和設置等操作,而是使用 sync.Map 的方法進行調用,Store 表示存儲,Load 表示獲取,Delete 表示刪除。
    • Sync.Map的鍵和值以interface{}類型進行保存
    • 使用 Range 配合一個回調函數進行遍歷操作,通過回調函數返回內部遍歷出來的值,Range 參數中回調函數的返回值在需要繼續迭代遍歷時,返回 true,終止迭代遍歷時,返回 false
    • Sync.Map沒有獲取map數量的方法,可以在Range自行計算
  2. package main
    
    import (
          "fmt"
          "sync"
    )
    
    func main() {
    
        var scene sync.Map
    
        // 將鍵值對保存到sync.Map
        scene.Store("key", "value")
    
        // 從sync.Map中根據鍵取值
        fmt.Println(scene.Load("key"))
    
        // 根據鍵刪除對應的鍵值對
        scene.Delete("key")
    
        // 遍歷所有sync.Map中的鍵值對
        scene.Range(func(k, v interface{}) bool {
            fmt.Println("iterate:", k, v)
            return true
        })
    }
    
  3. Sync.Map源碼解析

臨時對象池——Pool

Go是自動垃圾回收,在高性能場景下,不能任意產生太多垃圾,會造成gc負擔重

解決辦法: 使用pool來保存和複用臨時對象,減少內存分配和gc壓力

  1. Pool是一個可以分別存取的臨時對象的集合,是協程安全的,可以緩存申請但未使用的item用於之後的重用來減少GC的壓力

  2. Pool不能被複制

    • Get: 從池中選擇任意一個item,刪除池中的引用計數並提供給調用者,如果沒有取得item會返回new的結果
    • Put: 放入池中
    package main
    
    import (
    	"fmt"
    	"runtime"
    	"sync"
    )
    
    /**
     * 臨時對象池
     */
    
    func main() {
    
    	pool := sync.Pool{New: func() interface{} {
    		return 0
    	}}
    	pool.Put(1)
    	a := pool.Get()
    	fmt.Println(a)
      pool.Put(1)	
    	runtime.GC()	// gc並不會釋放池
    	b := pool.Get()
    	fmt.Println(b)
    }
    
    // 1
    // 1
    
  3. 爲什麼不叫cache而叫pool, 更多源碼的可以參考go夜讀的這個視頻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章