Maison  >  Article  >  développement back-end  >  Un article pour parler du problème de concurrence des ressources en langage Go

Un article pour parler du problème de concurrence des ressources en langage Go

青灯夜游
青灯夜游avant
2023-02-17 14:40:203238parcourir

Un article pour parler du problème de concurrence des ressources en langage Go

Nous savons tous que la sécurité des threads est très importante dans la programmation simultanée. Ensuite, nous supposerons un scénario pour reproduire la situation dangereuse du fil, puis expliquerons comment résoudre le

scénario

dans Go. Nous devons maintenant trouver leurs factorielles de 1 à 100 et mettre les résultats dans un. L'implémentation du code

1! = 1 = 1
2! = 1 * 2 = 2
3! = 1 * 2 * 3 = 6
4! = 1 * 2 * 3 * 4 = 24
5! = 1 * 2 * 3 * 4 * 5 = 120
...
{
    1: 1
    2: 2
    3: 6
    4: 24
    5: 120
    ...
}

dans la carte

var factorialMap = make(map[int]int)

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    factorialMap[n] = result
}

func main() {
    for i := 1; i < 10; i++ {
        Factorial(i)
    }
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

Un article pour parler du problème de concurrence des ressources en langage GoLe résultat de l'exécution du code ci-dessus ne pose en fait aucun problème. Pourquoi y a-t-il une situation dans le désordre ? Parce qu'il s'agit de la carte en langage Go, elle est en fait dans le désordre. D'après notre compréhension, la première stockée, la première sortie, mais désolé, la carte de Golang n'est pas comme ça. Il n'y a aucun problème avec l'exécution ci-dessus. Des étudiants prudents ont peut-être découvert que cette version du code n'utilise pas la concurrence, n'est-ce pas ? D'accord, continuons à améliorer la

implémentation de la concurrence

var factorialMap = make(map[int]int)

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    factorialMap[n] = result
}

func main() {
    for i := 1; i < 10; i++ {
        go Factorial(i)
    }
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

Un article pour parler du problème de concurrence des ressources en langage GoNous pouvons constater que la version concurrente ajoute simplement un go devant l'appel pour calculer la fonction factorielle. Ne sous-estimez pas ce go, c'est trop tiré par les cheveux. Bien sûr, tout le monde sait que c'est le mot-clé pour démarrer une coroutine dans le langage go. go而已。不要小看这个go,扯远了,当然大家知道这是go语言中开启一个协程的关键字即可。

执行结果就是,控制台啥都没输出,这是因为主协程和子协程之间的执行关系,下面我们画图理解

Un article pour parler du problème de concurrence des ressources en langage Go从上图中我们可以发现,主协程执行的时间短(表现在比较短),子协程执行时间比较长(表现在比较长) 我们一定要记住,子协程是相对于当前的主协程来说的,如果主协程不存在了,那就没有子协程了

所以上面代码啥都没输出就是因为,主协程已经执行完了,但是子协程还没做完,那子协程都没做完,factorialMap中能有东西吗?

主等子

这就引出我们第一个问题,主协程如何等待子协程执行完再退出程序。我们现在用一个最简单,最容易想到的做法

var factorialMap = make(map[int]int)

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    factorialMap[n] = result
}

func main() {
    for i := 1; i < 100; i++ {
        go Factorial(i)
    }
    time.Sleep(time.Second * 3)
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

Un article pour parler du problème de concurrence des ressources en langage Go当并发数比较小的时候,这个问题可能不会出现,一旦并发数变大,问题就立马出现了

图中的执行结果是并发map写入错误为什么会出现这个问题,我们假设100个人往一个篮子里放水果,很容易。但是100个人从一个篮子里拿水果,那就会出问题,首先,篮子里的水果不一定够100个,其二每个人都想先拿,必然会引起争抢。

问题一优化

针对上面的问题,我们引入全局锁的概念。这就有点像我们上厕所,100个人都想上厕所,但厕所只有1个,谁先抢到了谁先上,并且这个人还有给厕所上锁,防止其他人进来

Un article pour parler du problème de concurrence des ressources en langage Go

var factorialMap = make(map[int]int)
var lock sync.Mutex

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
            result *= i
    }
    // defer 不好理解
    // defer func(){
    // 	lock.Unlock() // 执行完解锁
    // }()
    lock.Lock() // 执行时上锁
    factorialMap[n] = result
    lock.Unlock() // 执行后解锁
}

func main() {
    for i := 1; i < 100; i++ {
        go Factorial(i)
    }
    time.Sleep(time.Second * 3)
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

Un article pour parler du problème de concurrence des ressources en langage Go执行结果有0可能是数据类型存不下了导致的,这个大家不用关心

Un article pour parler du problème de concurrence des ressources en langage Go这样我们就解决了资源竞争的问题了。但其实还有一个问题,就是我们在主协程中还是必须手动等待,这要非常不好,那如果子协程3秒内解决不了怎么办?

问题二优化

这个问题是我们不想在主协程中手动等待子协程,换句话说是我们不想直接在代码中写明要等待多长时间

这里我们就引入了WaitGroup

var factorialMap = make(map[int]int)
var lock sync.Mutex
var wg sync.WaitGroup

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    lock.Lock() // 执行时上锁
    factorialMap[n] = result
    lock.Unlock() // 执行后解锁
    wg.Done()
}

func main() {
    for i := 1; i < 100; i++ {
        wg.Add(1)
        go Factorial(i)
    }
    wg.Wait()
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

WaitGroup的内部原理大家自己细扣,我这就不讲了 总结来说就是WaitGroup

Le résultat de l'exécution est que rien n'est envoyé à la console. Cela est dû à la relation d'exécution entre la coroutine principale et la sous-coroutine. Faisons un dessin ci-dessous pour le comprendre

Un article pour parler du problème de concurrence des ressources en langage GoD'après l'image ci-dessus, nous pouvons constater que le temps d'exécution du La coroutine principale est courte (elle est relativement courte), le temps d'exécution de la sous-coroutine est relativement long (indiqué comme relativement long) Nous devons nous rappeler que la sous-coroutine est relative à la coroutine principale actuelle. Si la coroutine principale n'existe plus, il n'y aura pas de sous-coroutine. Donc le code ci-dessus ne produit rien car la coroutine principale a été exécutée. mais la sous-coroutine n'est pas terminée. Si la sous-coroutine n'est pas terminée, peut-il y avoir quelque chose dans factorialMap ?

Principale et autres sous-coroutines🎜🎜🎜Cela nous amène à notre première question, comment la coroutine principale attend-elle que la sous-coroutine termine son exécution avant de quitter le programme. Nous utilisons maintenant la manière la plus simple et la plus simple d'y penser🎜rrreee🎜 image .pngLorsque le nombre de concurrences est relativement faible, ce problème peut ne pas se produire. Une fois que le nombre de concurrences devient important, le problème apparaîtra immédiatement. Le résultat de l'exécution dans l'image est 🎜Carte simultanée erreur d'écriture 🎜Pourquoi ce problème se produit-il ? Supposons que 100 personnes mettent des fruits dans un panier. C'est très simple. Mais si 100 personnes prennent des fruits dans un panier, il y aura des problèmes. Premièrement, il n'y aura peut-être pas assez de fruits dans le panier. Deuxièmement, tout le monde voudra les prendre en premier, ce qui entraînera inévitablement une concurrence. 🎜

🎜Problème 1 Optimisation🎜🎜🎜Compte tenu des problèmes ci-dessus, nous introduisons le concept de verrouillage global. C'est un peu comme quand on va aux toilettes. 100 personnes veulent aller aux toilettes, mais il n'y a qu'une seule toilette. Celui qui l'attrapera en premier y ira en premier, et cette personne verrouille également les toilettes pour empêcher les autres d'entrer. 🎜Un article pour parler du problème de concurrence des ressources en langage Go🎜rrreee🎜Le résultat de l'exécution est 0, ce qui peut être dû au fait que le type de données ne peut pas être enregistré Oui, vous n'avez pas à vous en soucier🎜🎜Un article pour parler du problème de concurrence des ressources en langage GoDe cette façon, nous résolvons le problème de la concurrence des ressources. Mais il y a en fait un autre problème, c'est-à-dire que nous devons toujours attendre manuellement dans la coroutine principale, ce qui est très mauvais. Et si la sous-coroutine ne peut pas être résolue en 3 secondes ? 🎜

🎜Problème 2 Optimisation🎜🎜🎜Ce problème est que nous ne voulons pas attendre la sous-coroutine manuellement dans la coroutine principale. En d'autres termes, nous ne voulons pas. je veux écrire directement dans le code pour attendre Combien de temps🎜🎜Ici, nous avons introduit WaitGroup🎜rrreee🎜Vous pouvez étudier les principes internes de WaitGroup en détail, je n'entrerai pas dans les détails maintenant Pour résumer, WaitGroup est un panier. Chaque fois qu'une coroutine est ouverte, un identifiant (fonction Ajouter) est ajouté au panier. Chaque fois qu'une coroutine est exécutée, un identifiant est soustrait du panier (. Fonction Done), et enfin vérifiez le panier S'il est vide, cela signifie que la coroutine a été exécutée (fonction Wait)🎜🎜[Apprentissage recommandé : 🎜tutoriel vidéo go🎜]🎜

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