对于复杂类型如 container/list ,需要在所有读写操作上使用 sync.mutex 互斥锁以保证数据一致性,互斥锁并发情况下,Lock 操作会阻塞,一直等到其他线程Unlock,但是有的时候因为有一个耗时比较长的操作一直占用锁,我们想让其他线程不在Lock上一直阻塞,而是直接走其他业务流程。
举一个很简单的场景:在多人并发抽奖环节,为了保证不出现负库存,我们可以通过竞争锁,第一个获取锁的人可能中奖,而其他并发过来的请求获取锁失败(而不是一直阻塞在Lock()),直接当做未中奖处理。
思考了一下,有一个思路,我们可以用两道锁,第一道锁用来判断锁状态,第二道锁才是真正的耗时任务用的锁。
直接上代码
type NBLocker struct{
l1 sync.Mutex
l2 sync.Mutex
locked bool
}
func (NBLocker *NBLocker) Lock() (success bool) {
NBLocker.l1.Lock()
defer NBLocker.l1.Unlock()
if NBLocker.locked == false {
NBLocker.locked = true
success = true
NBLocker.l2.Lock()
}
return
}
func (NBLocker *NBLocker) Unlock() {
NBLocker.l1.Lock()
defer NBLocker.l1.Unlock()
NBLocker.locked = false
NBLocker.l2.Unlock()
}
使用
type foo struct {
mux NBLocker
}
func (self *foo) Bong(wg *sync.WaitGroup) {
defer wg.Done()
if !self.mux.Lock() {
fmt.Println("获取锁失败")
return
}
defer self.mux.Unlock()
time.Sleep(time.Second) //停顿一秒
fmt.Println("bong~")
}
func main() {
f := &foo{}
wg := &sync.WaitGroup{}
wg.Add(4)
go f.Bong(wg)
go f.Bong(wg)
go f.Bong(wg)
time.Sleep(time.Second *2)
go f.Bong(wg)
wg.Wait()
}
如果获取第二道锁失败,NBLocker.Lock() 方法会直接返回false,这时候只需要判断一下就可以直接跳过"抽奖环节"
以上例程输出
获取锁失败
获取锁失败
bong~
bong~