Kmutex. Lock mutex by unique ID.
Update 28 Aug 2016
My previous realization of kmutex was not really good. My logic didn’t assume a deep recursion. Reddit user epiris pointed on disadvantages of my realization and proposed really good alternative.
// Can be locked by unique ID
type Kmutex struct {
c *sync.Cond
l sync.Locker
s map[interface{}]struct{}
}
// Create new Kmutex
func New() *Kmutex {
l := sync.Mutex{}
return &Kmutex{c: sync.NewCond(&l), l: &l, s: make(map[interface{}]struct{})}
}We should use sync.Cond. Which allow to sleep gorutine by special condition. In our case that the map contain the special key.
func (km *Kmutex) locked(key interface{}) (ok bool) { _, ok = km.s[key]; return }
// Lock Kmutex by unique ID
func (km *Kmutex) Lock(key interface{}) {
km.l.Lock()
defer km.l.Unlock()
for km.locked(key) {
km.c.Wait()
}
km.s[key] = struct{}{}
return
}sync.Cond require a global mutex which also can guard the map in the same time.
// Unlock Kmutex by unique ID
func (km *Kmutex) Unlock(key interface{}) {
km.l.Lock()
defer km.l.Unlock()
delete(km.s, key)
km.c.Broadcast()
}After a key was removed from the map. sync.Cond broadcast that mutex is free to sleep gorutines.
Conclusion
Kmutex solves the problem when multiple gorutines try to access a part of resource by blocking the whole resource. Kmutex will only block the part of that particular data but lets the other gorutines access the other part for that resource.
DEPRECATED
Recently I faced with task: Guarantee that data will be consistent for mongoDB(anything which can’t be blocked but have unique ID for separate parts) . Request handler do subsequently do queries to db. Sometimes two request going to the same document. It’s system race condition.
With new sync.Map from go1.9 I will try to solve it . It could be easy implemented with a regular map guarded by a mutex. But with sync.Map code is more clear and short.
type Kmutex struct {
m *sync.Map
}
func NewKmutex() Kmutex {
m := sync.Map{}
return Kmutex{&m}
}I create a new Kmutex object via NewKmutex because sync.Map in no case should be copied.
func (s Kmutex) Lock(key interface{}) {
m := sync.Mutex{}
m_, _ := s.m.LoadOrStore(key, &m)
mm := m_.(*sync.Mutex)
mm.Lock()
if mm != &m {
mm.Unlock()
s.Lock(key)
return
}
return
}We acquire a mutex and try to set its pointer to map via LoadOrStore if map already contain pointer to a mutex from a different gorutine. Then sleep until the different gorutine finish and try to put in the map the own mutex via recursion.
func (s Kmutex) Unlock(key interface{}) {
l, exist := s.m.Load(key)
if !exist {
panic("kmutex: unlock of unlocked mutex")
}
l_ := l.(*sync.Mutex)
s.m.Delete(key)
l_.Unlock()
}After the gorutine finish. Unlock kmutex. Kmutex can cause panic if it will be unlocked before Lock.
Conclusion
Kmutex solves the problem when multiple gorutines try to access a part of resource by blocking the whole resource. Kmutex will only block the part of that particular data but lets the other gorutines access the other part for that resource.
