Home  >  Article  >  Backend Development  >  What causes my goroutine to get stuck in the following mutex code?

What causes my goroutine to get stuck in the following mutex code?

王林
王林forward
2024-02-13 18:57:07649browse

是什么导致我的 goroutine 在以下互斥体代码中陷入僵局?

php editor Shinichi is here to answer a common question: "What causes my goroutine to get stuck in the following mutex code?" In concurrent programming, use Mutex lock (Mutex) is one of the common methods to solve competition for shared resources. However, if there are some problems in the code, it may cause the goroutine to become deadlocked and unable to continue execution. Next, we will discuss in detail the possible causes of this problem and give solutions.

Question content

I am trying to save a map of keys with separate locks for each key. When creating a lock for a specific key, I use a global mutex to write to the map.

After I finish creating the lock for the key, I will use the new lock and release it when the work is done. Currently I'm trying to modify a single key to test my code.

This is the code:

// You can edit this code!
// Click here and start typing.
package main

import (
    "fmt"
    "sync"
    "time"
)

var count int
var globalMutex *sync.RWMutex
var mutexes map[int]*sync.Mutex

func MyLock(index int) {
    fmt.Println("Aquiring Lock")
    globalMutex.Lock()
    defer globalMutex.Unlock()

    count++

    if mutexes == nil {
        mutexes = make(map[int]*sync.Mutex)
    }

    if _, ok := mutexes[index]; !ok {
        mutexes[index] = &sync.Mutex{}
    }

    fmt.Println("Aquiring 2nd Lock")
    mutexes[index].Lock()
    fmt.Println("Aquired Lock")
}

func MyUnlock(index int) {
    globalMutex.Lock()
    defer globalMutex.Unlock()
    mutexes[index].Unlock()
}

func main() {
    var wg sync.WaitGroup
    globalMutex = &sync.RWMutex{}
    wg.Add(500)
    for i := 0; i < 500; i++ {
        go func(i int) {
            defer wg.Done()

            MyLock(2)

            time.Sleep(1 * time.Second)
            fmt.Println(i)

            MyUnlock(2)
        }(i)
    }

    wg.Wait()
    fmt.Println(mutexes)
    fmt.Println(count)
}

I'm not sure why it can't acquire the lock. Playground link: https://go.dev/play/p/-co0xaxpuy0

Solution

mylock can lock global mutexes and individual mutexes. This makes unlocking impossible in some cases:

  1. goroutine 1 calls mylock(2). This causes the single lock l2 to be locked as expected.
  2. goroutine 2 calls mylock(2). It acquires the global lock and then blocks waiting for l2 to be released. During the wait, the global lock continues to be held.
  3. goroutine 1 calls myunlock(2). It is blocked waiting for the global lock (held by goroutine 2) to be released. This is a deadlock.

To resolve this issue, return the individual locks while the global lock is held instead of (un)locking them. myunlock function becomes unnecessary: ​​

func mylock(index int) *sync.mutex {
    globalmutex.lock()
    defer globalmutex.unlock()

    count++

    if mutexes == nil {
        mutexes = make(map[int]*sync.mutex)
    }

    mu := mutexes[index]
    if mu == nil {
        mu = &sync.mutex{}
        mutexes[index] = mu
    }

    return mu
}

func main() {
    var wg sync.waitgroup
    globalmutex = &sync.rwmutex{}
    wg.add(500)
    for i := 0; i < 500; i++ {
        go func(i int) {
            defer wg.done()

            mu := mylock(2)
            mu.lock()
            defer mu.unlock()

            time.sleep(1 * time.second)
            fmt.println(i)
        }(i)
    }

    wg.wait()
    fmt.println(mutexes)
    fmt.println(count)
}

To improve performance, you can first check if a single lock exists, while only holding a global read lock (note that this changes what count represents):

func MyLock(index int) *sync.Mutex {
    globalMutex.RLock()
    mu := mutexes[index]
    globalMutex.RUnlock()

    if mu != nil {
        return mu
    }
    
    globalMutex.Lock()
    defer globalMutex.Unlock()

    count++

    if mutexes == nil {
        mutexes = make(map[int]*sync.Mutex)
    }

    mu = mutexes[index] // have to check again because
    if mu == nil {      // we briefly released globalMutex 
        mu = &sync.Mutex{}
        mutexes[index] = mu
    }

    return mu
}

The above is the detailed content of What causes my goroutine to get stuck in the following mutex code?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete