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