点击个人博客,查看更多文章https://elonjelinek.github.io
判断程序的执行结果,并解释原因
package main
import (
"fmt"
"sync"
)
const N = 20
func main() {
wg := sync.WaitGroup{}
mu := sync.Mutex{}
m := make(map[int]int)
wg.Add(N)
for i := 0; i < N; i++ {
go func() {
mu.Lock()
defer wg.Done()
m[i] = i
mu.Unlock()
}()
}
wg.Wait()
fmt.Println(len(m))
fmt.Println(m)
}
运行结果
3
map[12:13 13:13 20:20]
再次运行
5
map[15:16 20:20 10:12 12:12 13:13]
答案:结果不确定。原因:1、当N等于20的时候,主函数里启动了20个goroutine,并wait20个goroutine执行,只有当20个goroutine都执行完了以后,主函数才会结束,保证了map会被写20次。但是,这里虽然加了锁,加锁的原因却是map在并发中不是线程安全的,map不能并发的写,只可以并发的读取,所以这里不加锁就会报错。
打印i
package main
import (
"fmt"
"sync"
)
func main() {
var mg sync.WaitGroup
var mu sync.Mutex
const N = 20
m := make(map[int]int)
mg.Add(N)
for i := 0; i < N; i++ {
go func() {
mu.Lock()
m[i] = i
fmt.Println(m[i], i)
mu.Unlock()
mg.Done()
}()
}
mg.Wait()
}
运行结果
0 16
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
20 20
多运行几次,结果不是每次都相同。
这里加锁只是保证在一个goroutine操作map的时候,另一个goroutine不能操作map,但是这里的i,对于所有的goroutine来说,相当于一个全局变量,其变化是由主函数控制的,所以虽然启动了20个goroutine,map被写了20次,但是很多个goroutine拿到的i是同一个数,所以map被很多次写操作都被覆盖了,每次写的key,value都一样,所以map里并没有20对键值对,当N很小的时候,map里只有一个键值对,但是即便N足够大,map里键值对的数量也少于N对儿。
并且还会发生map的key与value并不相等的情况,也就是map拿到的i是在变化的。
如果想要往map里写入N对键值对,那么就必须给匿名函数传参,这样,每启动一个goroutine,拿到的都是不同的值,所以map的key全都不一样,所以每一次写入的数据都不会被覆盖,这时,map中就有N对键值对。
下面不加等待,打印一下i,会发现这个打印顺序是不确定的,i的值是不确定的,key与value也会不一样,并且有些value还会被覆盖。
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
const N = 20
m := make(map[int]int)
for i := 0; i < N; i++ {
go func() {
mu.Lock()
m[i] = i
fmt.Println(m[i],i,"----------------")
mu.Unlock()
}()
}
fmt.Println(len(m))
fmt.Println(len(m))
fmt.Println(m)
fmt.Println(len(m))
fmt.Println(len(m))
}
运行结果
0 13 ----------------
1
20 20 ----------------
20 20 ----------------
20 20 ----------------
20 20 ----------------
2
20 20 ----------------
20 20 ----------------
20 20 ----------------
20 20 ----------------
20 20 ----------------
map[4:13 20:20]
2
2
20 20 ----------------
20 20 ----------------
观察上面的结果,会发现,第一次打印的value是个0,但是后面打出来的map中并没有一个键值对的value是0。
这里带横线的那一行最终打印了12次,但是有11次,拿到的i都是同一个数20,也就是说map至少被执行了12次写操作,但是最终map的长度为2,里面只有两对键值对,而且有一对键值对的key与value并不相等。说明第一次打印的时候,map里面还是空的,并没有被写入数据,但是数据i已经拿到了,是13,而13对应的key是4,所以证明了12个goroutine拿到的数据i是个主函数里面的共享数据,【对12个goroutine来说,i相当于是个全局变量】,大家抢的是同一个数,而在并发执行抢占资源的过程中,for循环一直在执行,所以先抢到的i会比较小,会有4和13,但是后抢到的i,全都是20。
所以,只有给匿名函数传参的时候,12个goroutine拿到的数据才不是共享数据,而是每一个goroutine拿到的都是单独的数据。
这里打印了四次map长度,第一次打印了1,原因是这个时候,有的goroutine还没有执行完,而最后两行带横线的输出,是因为有些goroutine没来得及打印输出,但是对map的写操作已经完成了,所以才在最后两行打印,而不是已经输出了map,打印了长度之后,又有两个goroutine才启动,又执行了两次写操作。
传参以后,map中就有了N对键值对
package main
import (
"fmt"
"sync"
)
func main() {
wg := sync.WaitGroup{}
mu := sync.Mutex{}
m := make(map[int]int)
const N =20
wg.Add(N)
for i := 0; i < N; i++ {
go func(n int) {
defer wg.Done()
mu.Lock()
m[n] = n
mu.Unlock()
}(i)
}
wg.Wait()
fmt.Println(len(m))
fmt.Println(m)
}
运行结果
20
map[6:6 4:4 7:7 11:11 19:19 18:18 3:3 0:0 9:9 12:12 17:17 15:15 8:8 10:10 1:1 5:5 14:14 2:2 13:13 16:16]
点击个人博客,查看更多文章https://elonjelinek.github.io
本文通过一道Go语言面试题,探讨了goroutine、waitgroup和mutex在并发编程中的应用。分析了并发写入map时可能出现的问题,强调了在并发环境下对非线程安全数据结构的操作需要同步机制。通过示例展示了不加锁和加锁情况下map的执行结果,解释了加锁的意义以及如何正确地向map中写入N对键值对。
1634

被折叠的 条评论
为什么被折叠?



