文章來源:http://gf.johng.cn/os/gmlock/...html
內存鎖。該模塊包含兩個對象特性:git
Locker
內存鎖,支持按照給定鍵名生成內存鎖
,並支持Try*Lock
及鎖過時
特性;Mutex
對標準庫底層sync.Mutex
的封裝,增長了Try*Lock
特性;使用方式:安全
import "gitee.com/johng/gf/g/os/gmlock"
使用場景:併發
sync.Mutex
;Try*Lock
的場景(不須要阻塞等待鎖釋放);動態建立互斥鎖
,或者須要維護大量動態鎖
的場景;func Lock(key string, expire ...int) func RLock(key string, expire ...int) func RUnlock(key string) func TryLock(key string, expire ...int) bool func TryRLock(key string, expire ...int) bool func Unlock(key string) type Locker func New() *Locker func (l *Locker) Lock(key string, expire ...int) func (l *Locker) RLock(key string, expire ...int) func (l *Locker) RUnlock(key string) func (l *Locker) TryLock(key string, expire ...int) bool func (l *Locker) TryRLock(key string, expire ...int) bool func (l *Locker) Unlock(key string) type Mutex func NewMutex() *Mutex func (l *Mutex) Lock() func (l *Mutex) RLock() func (l *Mutex) RUnlock() func (l *Mutex) TryLock() bool func (l *Mutex) TryRLock() bool func (l *Mutex) Unlock()
package main import ( "time" "sync" "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gmlock" ) func main() { key := "lock" wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { gmlock.Lock(key) glog.Println(i) time.Sleep(time.Second) gmlock.Unlock(key) wg.Done() }(i) } wg.Wait() }
該示例中,模擬了同時開啓10
個goroutine,但同一時刻只能有一個goroutine得到鎖,得到鎖的goroutine執行1秒後退出,其餘goroutine才能得到鎖。日誌
執行後,輸出結果爲:code
2018-10-15 23:57:28.295 9 2018-10-15 23:57:29.296 0 2018-10-15 23:57:30.296 1 2018-10-15 23:57:31.296 2 2018-10-15 23:57:32.296 3 2018-10-15 23:57:33.297 4 2018-10-15 23:57:34.297 5 2018-10-15 23:57:35.297 6 2018-10-15 23:57:36.298 7 2018-10-15 23:57:37.298 8
咱們將以上的示例使用過時時間控制來實現。orm
package main import ( "sync" "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gmlock" ) func main() { key := "lock" wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { gmlock.Lock(key, 1000) glog.Println(i) wg.Done() }(i) } wg.Wait() }
執行後,輸出結果爲:htm
2018-10-15 23:59:14.663 9 2018-10-15 23:59:15.663 4 2018-10-15 23:59:16.663 0 2018-10-15 23:59:17.664 1 2018-10-15 23:59:18.664 2 2018-10-15 23:59:19.664 3 2018-10-15 23:59:20.664 6 2018-10-15 23:59:21.664 5 2018-10-15 23:59:22.665 7 2018-10-15 23:59:23.665 8
TryLock
方法是有返回值的,它表示用來嘗試獲取鎖,若是獲取成功,則返回true
;若是獲取失敗(即鎖已被其餘goroutine獲取),則返回false
。對象
package main import ( "sync" "gitee.com/johng/gf/g/os/glog" "time" "gitee.com/johng/gf/g/os/gmlock" ) func main() { key := "lock" wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { if gmlock.TryLock(key) { glog.Println(i) time.Sleep(time.Second) gmlock.Unlock(key) } else { glog.Println(false) } wg.Done() }(i) } wg.Wait() }
同理,在該示例中,同時也只有1
個goroutine能得到鎖,其餘goroutine在TryLock
失敗便直接退出了。進程
執行後,輸出結果爲:
2018-10-16 00:01:59.172 9 2018-10-16 00:01:59.172 false 2018-10-16 00:01:59.172 false 2018-10-16 00:01:59.172 false 2018-10-16 00:01:59.172 false 2018-10-16 00:01:59.172 false 2018-10-16 00:01:59.172 false 2018-10-16 00:01:59.172 false 2018-10-16 00:01:59.172 false 2018-10-16 00:01:59.176 false
該示例用來演示在複雜邏輯下的鎖機制處理狀況。
package main import ( "gitee.com/johng/gf/g/os/gmlock" "time" "gitee.com/johng/gf/g/os/glog" "fmt" ) // 內存鎖 - 手動Unlock與計時Unlock衝突校驗 func main() { key := "key" // 第一次鎖帶時間 gmlock.Lock(key, 1000) glog.Println("lock1") // 這個時候上一次的計時解鎖已失效 gmlock.Unlock(key) glog.Println("unlock1") fmt.Println() // 第二次鎖,不帶時間,且在執行過程當中前一個Lock的定時解鎖生效 gmlock.Lock(key) glog.Println("lock2") go func() { // 正常狀況下3秒後才能執行這句 gmlock.Lock(key) glog.Println("lock by goroutine") }() time.Sleep(3*time.Second) // 這時再解鎖 gmlock.Unlock(key) // 注意3秒以後纔會執行這一句 glog.Println("unlock2") // 阻塞進程 select{} }
執行後,輸出結果爲:
2018-10-16 00:03:40.277 lock1 2018-10-16 00:03:40.279 unlock1 2018-10-16 00:03:40.279 lock2 2018-10-16 00:03:43.279 unlock2 2018-10-16 00:03:43.279 lock by goroutine
在glog
模塊寫日誌文件的時候有這麼一個核心方法,咱們拿來看一下(源代碼位於 /g/os/glog/glog_logger.go)。
// 這裏的寫鎖保證同一時刻只會寫入一行日誌,防止串日誌的狀況 func (l *Logger) print(std io.Writer, s string) { // 優先使用自定義的IO輸出 if l.printHeader.Val() { s = l.format(s) } writer := l.GetWriter() if writer == nil { // 若是設置的writer爲空,那麼其次判斷是否有文件輸出設置 // 內部使用了內存鎖,保證在glog中對同一個日誌文件的併發寫入不會串日誌(併發安全) if f := l.getFilePointer(); f != nil { defer f.Close() key := l.path.Val() gmlock.Lock(key) _, err := io.WriteString(f, s) gmlock.Unlock(key) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) } } } else { l.doStdLockPrint(writer, s) } // 是否容許輸出到標準輸出 if l.alsoStdPrint.Val() { l.doStdLockPrint(std, s) } }
其中的:
gmlock.Lock(key) ... gmlock.Unlock(key)
便使用到了內存鎖的特性,其中的變量key
表示的是日誌文件的絕對路徑
,當多個goroutine對同一個日誌文件進行寫入時,由gmlock.Lock(key)
來保證對該文件的併發安全寫操做。