What is causing my goroutines to deadlock in the following mutex code?

huangapple go评论65阅读模式
英文:

What is causing my goroutines to deadlock in the following mutex code?

问题

我正在尝试为每个键保留一个具有单独锁的键映射。在为特定键创建锁时,我使用全局互斥锁来写入映射。

在为键创建锁完成后,我在完成工作后使用新的锁并释放它。
目前,我只是尝试修改一个键来测试我的代码。

以下是代码:

// 你可以编辑这段代码!
// 点击这里开始输入。
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("获取锁")
	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("获取第二个锁")
	mutexes[index].Lock()
	fmt.Println("已获取锁")
}

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)
}

我不确定为什么无法获取锁。
Playground链接:https://go.dev/play/p/-CO0xaXPuy0

英文:

I am trying to keep a map of keys with separate locks for each key.
While creating the lock for a specific key, I'm using a global mutex to write to the map.

After it is done creating the lock for a key, I'm using the new lock and releasing after I'm done with my job.
Currently I'm trying to modify a single key only to test my code.

Here is the code:

// You can edit this code!
// Click here and start typing.
package main
import (
&quot;fmt&quot;
&quot;sync&quot;
&quot;time&quot;
)
var count int
var globalMutex *sync.RWMutex
var mutexes map[int]*sync.Mutex
func MyLock(index int) {
fmt.Println(&quot;Aquiring Lock&quot;)
globalMutex.Lock()
defer globalMutex.Unlock()
count++
if mutexes == nil {
mutexes = make(map[int]*sync.Mutex)
}
if _, ok := mutexes[index]; !ok {
mutexes[index] = &amp;sync.Mutex{}
}
fmt.Println(&quot;Aquiring 2nd Lock&quot;)
mutexes[index].Lock()
fmt.Println(&quot;Aquired Lock&quot;)
}
func MyUnlock(index int) {
globalMutex.Lock()
defer globalMutex.Unlock()
mutexes[index].Unlock()
}
func main() {
var wg sync.WaitGroup
globalMutex = &amp;sync.RWMutex{}
wg.Add(500)
for i := 0; i &lt; 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 is failing to acquire lock.
Playground link: https://go.dev/play/p/-CO0xaXPuy0

答案1

得分: 1

我不完全确定你在尝试做什么,但是...

你的打印语句已经说明了情况。MyUnlock 尝试在另一个 goroutine 仍然持有全局互斥锁的情况下对其进行加锁,因为它正在等待你即将解锁的索引互斥锁。

英文:

I'm not entirely sure what you are trying to do with this, but ...

Your print statements tell the story. MyUnlock tries to lock the global mutex while another goroutine still has it locked because it is waiting on the indexed mutex you are about to unlock.

答案2

得分: 1

MyLock函数同时锁定全局互斥锁和个别互斥锁。这样在某些情况下就无法解锁:

  1. Goroutine 1调用MyLock(2)。这导致个别锁L2被锁定,符合预期。
  2. Goroutine 2调用MyLock(2)。它获取了全局锁,然后被阻塞等待L2被释放。在等待期间,全局锁仍然被持有。
  3. Goroutine 1调用MyUnlock(2)。它被阻塞等待全局锁(由goroutine 2持有)被释放。这就是死锁。

为了解决这个问题,在持有全局锁的同时,返回个别锁而不是对它们进行(解)锁定。MyUnlock函数变得不再必要:

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)
}

为了提高性能,你可以在仅持有全局读锁的情况下首先检查个别锁是否存在(注意这会改变count的含义):

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] // 必须再次检查,因为我们短暂地释放了globalMutex
    if mu == nil {      // we briefly released globalMutex 
        mu = &sync.Mutex{}
        mutexes[index] = mu
    }

    return mu
}
英文:

MyLock locks both the global and the individual mutexes. This makes it impossible to unlock in some circumstances:

  1. Goroutine 1 calls MyLock(2). This causes the individual lock L2 to be locked, as intended.
  2. Goroutine 2 calls MyLock(2). It acquires the global lock and is then blocked waiting for L2 to be released. While it is waiting, 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 the deadlock.

To fix this, return the individual locks instead of (un)locking them while the global lock is held. The 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 = &amp;sync.Mutex{}
        mutexes[index] = mu
    }

    return mu
}

func main() {
    var wg sync.WaitGroup
    globalMutex = &amp;sync.RWMutex{}
    wg.Add(500)
    for i := 0; i &lt; 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 check if the individual lock exists first while holding only 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 = &amp;sync.Mutex{}
        mutexes[index] = mu
    }

    return mu
}

huangapple
  • 本文由 发表于 2023年4月18日 13:48:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76041138.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定