互斥鎖
使用互斥鎖能夠保證同一時間有且只有一個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包的函數/類型實現同步更好。