- Created by 郭强, last modified by 海亮 on Jun 28, 2022
gmutex
模块是基于atomic
+ channel
实现的高级互斥锁模块,支持更丰富的互斥锁特性。
gmutex.Mutex
互斥锁对象支持读写控制,互斥锁功能逻辑与标准库sync.RWMutex
类似,可并发读但不可并发写。
互斥锁的设计细节,推荐阅读轻量级高清版的实现源码:https://github.com/gogf/gf/blob/master/os/gmutex/gmutex.go
使用方式:
import "github.com/gogf/gf/v2/os/gmutex"
接口文档:
https://pkg.go.dev/github.com/gogf/gf/v2/os/gmutex
type Mutex func New() *Mutex func (m *Mutex) IsLocked() bool func (m *Mutex) IsRLocked() bool func (m *Mutex) IsWLocked() bool func (m *Mutex) Lock() func (m *Mutex) LockFunc(f func()) func (m *Mutex) RLock() func (m *Mutex) RLockFunc(f func()) func (m *Mutex) RUnlock() func (m *Mutex) TryLock() bool func (m *Mutex) TryLockFunc(f func()) bool func (m *Mutex) TryRLock() bool func (m *Mutex) TryRLockFunc(f func()) bool func (m *Mutex) Unlock()
- 该互斥锁模块最大的特点是支持
Try*
方法以及*Func
方法。 Try*
方法用于实现尝试获得特定类型的锁,如果获得锁成功则立即返回true
,否则立即返回false
,不会阻塞等待,这对于需要使用非阻塞锁机制的业务逻辑非常实用。*Func
方法使用闭包匿名函数的方式实现特定作用域的并发安全锁控制,这对于特定代码块的并发安全控制特别方便,由于内部使用了defer
来释放锁,因此即使函数内部产生异常错误,也不会影响锁机制的安全性控制。
基准测试
gmutex.Mutex
与标准库的sync.Mutex
及sync.RWMutex
的基准测试对比结果: gmutex_bench_test.go
goos: linux goarch: amd64 pkg: github.com/gogf/gf/v2/os/gmutex Benchmark_Mutex_LockUnlock-4 50000000 31.5 ns/op Benchmark_RWMutex_LockUnlock-4 30000000 54.1 ns/op Benchmark_RWMutex_RLockRUnlock-4 50000000 27.9 ns/op Benchmark_GMutex_LockUnlock-4 50000000 27.2 ns/op Benchmark_GMutex_TryLock-4 100000000 16.7 ns/op Benchmark_GMutex_RLockRUnlock-4 50000000 38.0 ns/op Benchmark_GMutex_TryRLock-4 100000000 16.8 ns/op
示例1,基本使用
package main import ( "time" "github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/os/gmutex" ) func main() { mu := gmutex.New() for i := 0; i < 10; i++ { go func(n int) { mu.Lock() defer mu.Unlock() glog.Println("Lock:", n) time.Sleep(time.Second) }(i) } for i := 0; i < 10; i++ { go func(n int) { mu.RLock() defer mu.RUnlock() glog.Println("RLock:", n) time.Sleep(time.Second) }(i) } time.Sleep(11 * time.Second) }
执行后,终端输出:
2019-07-13 16:19:55.417 Lock: 0 2019-07-13 16:19:56.421 Lock: 1 2019-07-13 16:19:57.424 RLock: 0 2019-07-13 16:19:57.424 RLock: 4 2019-07-13 16:19:57.425 RLock: 8 2019-07-13 16:19:57.425 RLock: 2 2019-07-13 16:19:57.425 RLock: 7 2019-07-13 16:19:57.425 RLock: 5 2019-07-13 16:19:57.425 RLock: 9 2019-07-13 16:19:57.425 RLock: 1 2019-07-13 16:19:57.425 RLock: 6 2019-07-13 16:19:57.425 RLock: 3 2019-07-13 16:19:58.429 Lock: 3 2019-07-13 16:19:59.433 Lock: 4 2019-07-13 16:20:00.438 Lock: 5 2019-07-13 16:20:01.443 Lock: 6 2019-07-13 16:20:02.448 Lock: 7 2019-07-13 16:20:03.452 Lock: 8 2019-07-13 16:20:04.456 Lock: 9 2019-07-13 16:20:05.461 Lock: 2
这里使用glog
打印的目的,是可以方便地看到打印输出的时间。可以看到,在第3秒的时候,读锁抢占到了机会,由于gmutex.Mutex
对象支持并发读但不支持并发写,因此读锁抢占后迅速执行完毕;而写锁依旧保持每秒打印一条日志继续执行。
示例2,*Func
使用
package main import ( "time" "github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/os/gmutex" ) func main() { mu := gmutex.New() go mu.LockFunc(func() { glog.Println("lock func1") time.Sleep(1 * time.Second) }) time.Sleep(time.Millisecond) go mu.LockFunc(func() { glog.Println("lock func2") }) time.Sleep(2 * time.Second) }
执行后,终端输出:
2019-07-13 16:28:10.381 lock func1 2019-07-13 16:28:11.385 lock func2
可以看到,使用*Func
方法实现特定作用域的锁控制非常方便。
Content Menu
- No labels
4 Comments
朱华 Hunk
有个疑问,先加读锁,在读锁期间再加写锁,会不会形成死锁?
郭强
标准库的话会死锁,用
gmutex
的话你可以TryLock
。朱华 Hunk
RLock → Lock → UnLock → RUnLock 这样还是会死锁嘛?看样子,还是只有先RUnlock再lock了,只是这样子得重复判断一次RLock中判断过的事情。
要是能直接提升锁级别就方便了,比如在需要时,能直接把读锁提升为写锁。
hanpengzhou
基准测试结果的文件地址找不到了.