I want write a mutex with key just like https://github.com/im7mortal/kmutex ,but in different ideas.Just store the mutex in the hashmap.But there are always some deadlock in my code.How can I find the bug? Or are there better ways to write the key mutex?

package kmutex

import (
    "sync"
)

type keyMutex struct {
    localLockMap map[string]*sync.Mutex
    globalLock   sync.Mutex
}

func NewKeyMutex() *keyMutex {
    return &keyMutex{localLockMap: map[string]*sync.Mutex{}}
}

func (km *keyMutex) Lock(key string) {
    km.globalLock.Lock()

    wl, ok := km.localLockMap[key]

    if !ok {
        wl = &sync.Mutex{}
        km.localLockMap[key] = wl
    }

    km.globalLock.Unlock()

    wl.Lock()
}

func (km *keyMutex) Unlock(key string) {
    km.globalLock.Lock()

    wl, ok := km.localLockMap[key]

    if !ok {
        km.globalLock.Unlock()
        return
    }

    delete(km.localLockMap, key)

    km.globalLock.Unlock()

    wl.Unlock()
}

and test code is below

func TestKeyMutex1(t *testing.T) {

    keyMutex := kmutex.NewKeyMutex()

    //var keyMutex sync.Mutex

    var count = 0

    var wg sync.WaitGroup

    var num = 100

    for i := 1; i <= num; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            keyMutex.Lock("a")
            count += i
            keyMutex.Unlock("a")
        }(i)
    }

    wg.Wait()

    println(count)

}

There are always deadlock in it.After I remove the line delete(km.localLockMap, key).The deadlock is gone! But I still cannot understand it

2 Answers

4
leaf bebop On

The problem is indeed deleting the mutex in the map.

Considering just have three locks in the same entry ("a" in your case) running in the following order (in multiple goroutines, of course):

keyMutex.Lock("a") // lock1
keyMutex.Lock("a") // lock2
keyMutex.Lock("a") // lock3
keyMutex.Unlock("a") // unlock1
keyMutex.Unlock("a") // unlock2
keyMutex.Unlock("a") // unlock3

lock1 creates a mutex in the map, and then lock2 and lock3 acquire the same mutex, blocking on the Lock of the mutex. unlock1 finds the mutex, unlocks it and removes it from the map, unblocking lock2 (or lock3, but for discussion's sake, let's say it is lock2). But when unlock2 or unlock3 runs, it finds no mutex in the map, unlocking nothing, and keeps lock3 blocking, thus a deadlock.

The line removing mutex from the map simply don't make sense: a mutex is re-usable and in the context it is only sensible to have a same mutex for same entry.

0
svetha.cvl On

You could check out sync.Map as well where the mutex primitives are abstracted for you. From the official documentation,

Map is like a Go map[interface{}]interface{} but is safe for concurrent use by multiple goroutines without additional locking or coordination. Loads, stores, and deletes run in amortized constant time.