Maison  >  Article  >  développement back-end  >  Qu'est-ce qui fait que ma goroutine reste bloquée dans le code mutex suivant ?

Qu'est-ce qui fait que ma goroutine reste bloquée dans le code mutex suivant ?

王林
王林avant
2024-02-13 18:57:07649parcourir

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

L'éditeur PHP Xinyi est là pour répondre à une question courante : "Qu'est-ce qui fait que ma goroutine reste bloquée dans le code mutex suivant ? En programmation simultanée, l'utilisation de verrous mutex (Mutex) est courante. L'une des façons de résoudre concurrence pour les ressources partagées. Cependant, s'il y a des problèmes dans le code, la goroutine peut se retrouver bloquée et incapable de poursuivre son exécution. Ensuite, nous discuterons en détail des causes possibles de ce problème et proposerons des solutions.

Contenu de la question

J'essaie d'enregistrer une carte de clés, chaque clé a une serrure séparée. Lors de la création d'un verrou pour une clé spécifique, j'utilise un mutex global pour écrire sur la carte.

Une fois la création du verrou pour la clé terminée, j'utiliserai le nouveau verrou et je le libérerai une fois le travail terminé. Actuellement, j'essaie de modifier une seule clé pour tester mon code.

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

Je ne sais pas pourquoi il ne peut pas acquérir le verrou. Lien Playground : https://go.dev/play/p/-co0xaxpuy0

Solution

mylock peut verrouiller les mutex globaux et individuels. Cela rend le déverrouillage impossible dans certains cas :

  1. goroutine 1 appelle mylock(2). Cela provoque le verrouillage du verrou unique l2 comme prévu.
  2. goroutine 2 appelle mylock(2). Il acquiert le verrou global puis bloque en attendant que l2 soit libéré. Pendant l'attente, le verrouillage global continue d'être maintenu.
  3. goroutine 1 appelle myunlock(2). Il est bloqué en attendant que le verrou global (détenu par la goroutine 2) soit libéré. C'est une impasse.

Pour résoudre ce problème, remettez les verrous individuels tout en maintenant le verrou global au lieu de les (dé)verrouiller. La fonction myunlock devient inutile :

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

Pour améliorer les performances, vous pouvez d'abord vérifier si un seul verrou existe, tout en ne détenant qu'un verrou global en lecture (notez que cela change ce que représente 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] // have to check again because
    if mu == nil {      // we briefly released globalMutex 
        mu = &sync.Mutex{}
        mutexes[index] = mu
    }

    return mu
}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer