文章來源:http://gf.johng.cn/os/gmlock/…
記憶體鎖。該模組包含兩個物件特性:
-
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()
示例1,基本使用
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才能獲得鎖。
執行後,輸出結果為:
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
示例2,過期控制
我們將以上的示例使用過期時間控制來實現。
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()
}
執行後,輸出結果為:
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
示例3,TryLock非阻塞鎖
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
示例4,多個鎖機制衝突
該示例用來演示在複雜邏輯下的鎖機制處理情況。
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
示例5,多檔案併發寫的安全控制
在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)
來保證對該檔案的併發安全寫操作。