文章来源: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)
来保证对该文件的并发安全写操做。