github.com/decred/dcrlnd@v0.7.6/multimutex/hash_mutex.go (about) 1 package multimutex 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/decred/dcrlnd/lntypes" 8 ) 9 10 // HashMutex is a struct that keeps track of a set of mutexes with a given hash. 11 // It can be used for making sure only one goroutine gets given the mutex per 12 // hash. 13 type HashMutex struct { 14 // mutexes is a map of hashes to a cntMutex. The cntMutex for 15 // a given hash will hold the mutex to be used by all 16 // callers requesting access for the hash, in addition to 17 // the count of callers. 18 mutexes map[lntypes.Hash]*cntMutex 19 20 // mapMtx is used to give synchronize concurrent access 21 // to the mutexes map. 22 mapMtx sync.Mutex 23 } 24 25 // NewHashMutex creates a new Mutex. 26 func NewHashMutex() *HashMutex { 27 return &HashMutex{ 28 mutexes: make(map[lntypes.Hash]*cntMutex), 29 } 30 } 31 32 // Lock locks the mutex by the given hash. If the mutex is already 33 // locked by this hash, Lock blocks until the mutex is available. 34 func (c *HashMutex) Lock(hash lntypes.Hash) { 35 c.mapMtx.Lock() 36 mtx, ok := c.mutexes[hash] 37 if ok { 38 // If the mutex already existed in the map, we 39 // increment its counter, to indicate that there 40 // now is one more goroutine waiting for it. 41 mtx.cnt++ 42 } else { 43 // If it was not in the map, it means no other 44 // goroutine has locked the mutex for this hash, 45 // and we can create a new mutex with count 1 46 // and add it to the map. 47 mtx = &cntMutex{ 48 cnt: 1, 49 } 50 c.mutexes[hash] = mtx 51 } 52 c.mapMtx.Unlock() 53 54 // Acquire the mutex for this hash. 55 mtx.Lock() 56 } 57 58 // Unlock unlocks the mutex by the given hash. It is a run-time 59 // error if the mutex is not locked by the hash on entry to Unlock. 60 func (c *HashMutex) Unlock(hash lntypes.Hash) { 61 // Since we are done with all the work for this 62 // update, we update the map to reflect that. 63 c.mapMtx.Lock() 64 65 mtx, ok := c.mutexes[hash] 66 if !ok { 67 // The mutex not existing in the map means 68 // an unlock for an hash not currently locked 69 // was attempted. 70 panic(fmt.Sprintf("double unlock for hash %v", 71 hash)) 72 } 73 74 // Decrement the counter. If the count goes to 75 // zero, it means this caller was the last one 76 // to wait for the mutex, and we can delete it 77 // from the map. We can do this safely since we 78 // are under the mapMtx, meaning that all other 79 // goroutines waiting for the mutex already 80 // have incremented it, or will create a new 81 // mutex when they get the mapMtx. 82 mtx.cnt-- 83 if mtx.cnt == 0 { 84 delete(c.mutexes, hash) 85 } 86 c.mapMtx.Unlock() 87 88 // Unlock the mutex for this hash. 89 mtx.Unlock() 90 }