Maison > Article > développement back-end > 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
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 ... }
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) } }
Le 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
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) } }
Nous 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语言中开启一个协程的关键字即可。
执行结果就是,控制台啥都没输出,这是因为主协程和子协程之间的执行关系,下面我们画图理解
从上图中我们可以发现,主协程执行的时间短(表现在比较短),子协程执行时间比较长(表现在比较长) 我们一定要记住,子协程是相对于当前的主协程来说的,如果主协程不存在了,那就没有子协程了
所以上面代码啥都没输出就是因为,主协程已经执行完了,但是子协程还没做完,那子协程都没做完,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) } }
当并发数比较小的时候,这个问题可能不会出现,一旦并发数变大,问题就立马出现了
图中的执行结果是并发map写入错误为什么会出现这个问题,我们假设100个人往一个篮子里放水果,很容易。但是100个人从一个篮子里拿水果,那就会出问题,首先,篮子里的水果不一定够100个,其二每个人都想先拿,必然会引起争抢。
针对上面的问题,我们引入全局锁的概念。这就有点像我们上厕所,100个人都想上厕所,但厕所只有1个,谁先抢到了谁先上,并且这个人还有给厕所上锁,防止其他人进来
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) } }
执行结果有0可能是数据类型存不下了导致的,这个大家不用关心
这样我们就解决了资源竞争的问题了。但其实还有一个问题,就是我们在主协程中还是必须手动等待,这要非常不好,那如果子协程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
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!