簡介
在Java中提供Sychronized
關鍵字提供獨佔鎖,Lock
類提供讀寫鎖。在sync包中實現的功能也是與鎖相關,包中主要包含的有:
- sync.Map:併發安全 map
- sync.Mutex:鎖
- sync.RWMutex:讀寫鎖
- sync.Once:只執行一次
- sync.WaitGroup: goroutine 之間同步
- sync.Pool:複用緩存池
sync.Map
key 和 value 類型都是 Any
。意味着你要搞各 種類型斷言
使用示例
package main
import (
"fmt"
"sync"
)
func main() {
m := sync.Map{}
m.Store("cat", "Tom")
m.Store("mouse", "Jerry")
// 這裏重新讀取出來的,就是
val, ok := m.Load("cat")
if ok {
fmt.Println(len(val.(string)))
fmt.Printf("%s \n", val)
}
}
sync.Mutex 和sync.RWMutex
使用案例
package main
import (
"sync"
)
var mutex sync.Mutex
var rwMutex sync.RWMutex
func Mutex() {
mutex.Lock()
defer mutex.Unlock()
// 你的代碼
}
func RwMutex() {
// 加讀鎖
rwMutex.RLock()
defer rwMutex.RUnlock()
// 也可以加寫鎖
rwMutex.Lock()
defer rwMutex.Unlock()
}
// 不可重入例子
func Failed1() {
mutex.Lock()
defer mutex.Unlock()
// 這一句會死鎖
// 但是如果你只有一個goroutine,那麼這一個會導致程序崩潰
mutex.Lock()
defer mutex.Unlock()
}
// 不可升級
func Failed2() {
rwMutex.RLock()
defer rwMutex.RUnlock()
// 這一句會死鎖
// 但是如果你只有一個goroutine,那麼這一個會導致程序崩潰
mutex.Lock()
defer mutex.Unlock()
}
mutex家族注意事項
- 儘量用 RWMutext
- 儘量用 defer 來釋放鎖,防止panic沒有釋放鎖
- 不可重入:lock 之後,即便是同一個線程(goroutine),也無法再次加鎖(寫遞歸函數要小心)
- 不可升級:加了讀鎖之後,如果試圖加寫鎖,鎖不升級
不可重入和不可升級,和很多語言的實現都是不同 的,因此要小心使用
sync.Once
package main
import (
"fmt"
"sync"
)
func main() {
PrintOnce()
PrintOnce()
PrintOnce()
}
var once sync.Once
// 這個方法,不管調用幾次,只會輸出一次
func PrintOnce() {
once.Do(func() {
fmt.Println("只輸出一次")
})
}
sync.WaitGroup
WaitGroup,它有3個函數:
- Add():在被等待的協程啓動前加1,代表要等待1個協程。
- Done():被等待的協程執行Done,代表該協程已經完成任務,通知等待協程。
- Wait(): 等待其他協程的協程,使用Wait進行等待。
type WaitGroup
func (wg *WaitGroup) Add(delta int){}
func (wg *WaitGroup) Done(){}
func (wg *WaitGroup) Wait(){}
下怎麼用WaitGroup實現上面的問題。
隊長先創建一個WaitGroup對象wg,
每個隊員都是1個協程, 隊長讓隊員出發前,使用wg.Add(),
隊員出發尋找鑰匙,隊長使用wg.Wait()等待(阻塞)所有隊員完成,
某個隊員完成時執行wg.Done(),等所有隊員找到鑰匙,
wg.Wait()則返回,完成了等待的過程,接下來就是開箱。
package main
import (
"fmt"
"sync"
)
func main() {
// 鑰匙
key := 0
// 資本家隊長創建隊伍
wg := sync.WaitGroup{}
// 這個隊伍十個人
wg.Add(10)
for i := 1; i <= 10; i++ {
go func(val int) {
key = val
// 打工人隊員找到了鑰匙
wg.Done()
}(i)
}
// 隊長等待隊伍集合,拿回鑰匙,如果不等待,鑰匙數量不定
// 把這個註釋掉你會發現,鑰匙數量不知道會有多少個
wg.Wait()
fmt.Println(key)
}
sync.Pool
pool是什麼
Golang在 1.3 版本的時候,在sync包中加入一個新特性:Pool。 簡單的說:就是一個臨時對象池。
爲什麼需要sync.pool
保存和複用臨時對象,減少內存分配,降低GC壓力
對象越多GC越慢,因爲Golang進行三色標記回收的時候,要標記的也越多,自然就慢了
sync.pool 特性
- 優先從自己的緩存裏面返回數據
- 如果不夠了,就創建一個新的,使用傳入的 New 回調
- 在 GC 的時候,sync.Pool 會被清空
sync.pool 缺陷
- 沒有超時機制
- 無法限制內存使用量
- GC 全部清掉
使用案例
sync.Pool 的一般使用步驟(以user爲例)
- 定義 user 結構體
- 爲 user 加上 Reset 方法。該方法用於重置對象, 接收器是指針
- 定義一個 sync.Pool
- 調用 Get 方法
- 使用defer 保證放回去 pool
- 重置 user
- 執行業務邏輯
package main
import (
"fmt"
"sync"
)
func main() {
// 初始化一個pool
pool := sync.Pool{
// 默認的返回值設置,不寫這個參數,默認是nil
New: func() interface{} {
return &user{}
}}
// Get 返回的是 interface{},所以需要類型斷言
// Get 方法會返回 Pool 已經存在的對象;如果沒有就使用New方法創建.
u := pool.Get().(*user)
// 對象或資源不用時,調用 Put 方法把對象或資源放回池子,
// 池子裏面的對象啥時候真正釋放是由 go_runtime進行回收,是不受外部控制的
// defer 還回去
defer pool.Put(u)
// 緊接着重置 u 這個對象
u.Reset("Tom", "[email protected]")
// 下邊就是使用 u 來完成你的業務邏輯
fmt.Printf("%+v", u)
}
type user struct {
Name string
Email string
}
// 一般來說,複用對象都要求我們取出來之後,
// 重置裏面的字段
func (u *user) Reset(name string, email string) {
u.Email = email
u.Name = name
}