Go語言基礎之併發(併發安全和鎖)

互斥鎖

使用互斥鎖能夠保證同一時間有且只有一個goroutine進入臨界區,其他的goroutine則在等待鎖;當互斥鎖釋放後,等待的goroutine纔可以獲取鎖進入臨界區,多個goroutine同時等待一個鎖時,喚醒的策略是隨機的。

package main

import (
	"fmt"
	"sync"
)

var global int64
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
	defer wg.Done()
	for i := 0; i < 5000; i++ {
		lock.Lock() // 加鎖
		global++
		lock.Unlock() // 解鎖
	}
}
func main() {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(global)
}

讀寫鎖

對於讀多寫少的場景下使用讀寫鎖性能會比互斥鎖好。
寫鎖優先級高,寫鎖獨佔,讀鎖共享。

package main

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

var (
	wg     sync.WaitGroup
	lock   sync.Mutex
	rwlock sync.RWMutex
)

func write() {
	// lock.Lock()   // 加互斥鎖
	rwlock.Lock()                     // 加寫鎖
	time.Sleep(10 * time.Millisecond) // 假設讀操作耗時10毫秒
	rwlock.Unlock()                   // 解寫鎖
	// lock.Unlock()                     // 解互斥鎖
	wg.Done()
}

func read() {
	// lock.Lock()                  // 加互斥鎖
	rwlock.RLock()               // 加讀鎖
	time.Sleep(time.Millisecond) // 假設讀操作耗時1毫秒
	rwlock.RUnlock()             // 解讀鎖
	// lock.Unlock()                // 解互斥鎖
	wg.Done()
}

func main() {
	start := time.Now()
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go write()
	}

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go read()
	}

	wg.Wait()
	end := time.Now()
	fmt.Println(end.Sub(start))
}

在代碼中生硬的使用time.Sleep肯定是不合適的,Go語言中可以使用sync.WaitGroup來實現併發任務的同步。

sync.Once實現單例模式

Go語言中的sync包中提供了一個針對只執行一次場景的解決方案–sync.Once。
sync.Once只有一個Do方法,其簽名如下:

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

備註:如果要執行的函數f需要傳遞參數就需要搭配閉包來使用。

package main

import "sync"

type singleton struct{}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
	once.Do(func() {
		instance = &singleton{}
	})
	return instance
}
func main() {
	GetInstance()
}

sync.Map

Go語言中內置的map不是併發安全的。

package main

import (
	"fmt"
	"strconv"
	"sync"
)

var safeMap = sync.Map{}

func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 20; i++ {
		go func(n int) {
			wg.Add(1)
			key := strconv.Itoa(n)
			safeMap.Store(key, n)
			val, ok := safeMap.Load(key) //
			if ok {
				fmt.Println("key:", key, "val:", val)
			}
			wg.Done()
		}(i)
	}
	wg.Wait()
}

普通的map則會報錯。

atomic原子變量

測試一下對於變量自增操作,加鎖、使用atomic原子變量三個的性能差別:

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

//Counter 定義一個公共的接口
type Counter interface{
	Increment()
	Load() int64
}
//NonLockCounter 無鎖的自增變量結構體
type NonLockCounter struct{
	val int64
}
//Increment NonLockCounter結構體的自增方法
func(n *NonLockCounter)Increment(){
	n.val++
}
//Load NonLockCounter的Load方法
func(n *NonLockCounter)Load()int64{
	return n.val
}
//LockCounter 帶互斥鎖的自增變量結構體
type LockCounter struct{
	val int64
	lock sync.Mutex
}
//Increment 帶互斥鎖的自增變量結構體的自增方法
func(l *LockCounter)Increment(){
	l.lock.Lock()
	defer l.lock.Unlock()
	l.val++
}
//Load 帶互斥鎖的自增變量結構體的Load方法
func(l* LockCounter)Load()int64{
	l.lock.Lock()
	defer l.lock.Unlock()	
	return l.val
}
//AtomicCounter 使用atomic庫的原子操作的結構體
type AtomicCounter struct{
	val int64
}
//Increment 原子操作的自增方法
func(a *AtomicCounter)Increment(){
	atomic.AddInt64(&a.val,1)
}
//Load 原子操作的Load方法
func(a *AtomicCounter)Load()int64{
	return atomic.LoadInt64(&a.val)
}

func test(c Counter){
	wg:=sync.WaitGroup{}
	start:=time.Now()
	for i:=0;i<1000;i++{
		wg.Add(1)
		go func(){
			c.Increment()
			wg.Done()
		}()
	}
	wg.Wait()
	end:=time.Now()
	fmt.Println(c.Load(),end.Sub(start))
}
func main(){
	c1:=NonLockCounter{}
	c2:=LockCounter{}
	c3:=AtomicCounter{}
	test(&c1)
	test(&c2)
	test(&c3)
}

971 1.9977ms
1000 1.001ms
1000 0s
atomic包提供了底層的原子級內存操作,對於同步算法的實現很有用。這些函數必須謹慎地保證正確使用。除了某些特殊的底層應用,使用通道或者sync包的函數/類型實現同步更好。

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