go语言多协程出现并发写的问题?

小伟男
  • 42
江苏

为什么多协程会出现并发写的问题呢?协程间不是并发运行的吗?应该不会在同一时刻有多个协程操作同一个数据,这块儿不理解希望有大牛给解释解释到底是因为什么,谢谢!

package main

import (
    "fmt"
    "time"
)

//现在要计算1-20的各个数的阶乘,并且把各个数的阶乘放入到map中。最后显示出来。要求使用goroutine完成

var res = make(map[int]int)

// factorial计算n的阶乘
func factorial(n int) {
    tmp := 1
    for i := 1; i <= n; i++ {
        tmp *= i
    }
    res[n] = tmp
}

func main() {
    //runtime.GOMAXPROCS(1)
    for i := 1; i <= 20; i++ {
        go factorial(i) // 开启协程计算每个数的阶乘
    }
    time.Sleep(time.Second * 10)
    for i, v := range res {
        fmt.Printf("map[%d]=%d\n", i, v)
    }
}

image.png

回复
阅读 304
3 个回答

报错信息很准确,concurrent map writes

以下操作可以视作,20个协程并发写 res

res[n] = tmp

for i := 1; i <= 20; i++ {
    go factorial(i) // 开启协程计算每个数的阶乘
}

resmap[int]int 类型,不是并发安全的类型;

写的时候调用了 runtime 包的 mapassign_fast64(t *maptype, h *hmap, key uint64) 方法返回地址,再对该地址指向的内存赋值;

某一个协程对 map 底层进行扩张后,另一个协程可能使用的还是原来的底层结构。

就你这里的,改成用切片?res = make([]int, 21)

海生
  • 34
上海市浦东新区环科路

问题一:

for i := 1; i <= 20; i++ {
    go factorial(i) // 此时i的变量是 整个for循环内的,所以i会变。
}

问题二:

func factorial(n int) {
    tmp := 1
    for i := 1; i <= n; i++ {
        tmp *= i
    }
    res[n] = tmp
}

这里的n,就是你传进来的i,他的值会一样。

问题三:
现在是多核cpu,是可以同一个时刻,两个cpu同时写入map的。

修改后:

func main() {
    lock := sync.Mutex{}
    for i := 1; i <= 20; i++ {
        // 开启协程计算每个数的阶乘
        go func(j int) {
            lock.Lock()
            defer lock.Unlock()
            factorial(j)
        }(i)
    }
    time.Sleep(time.Second * 10)
    for i, v := range res {
        fmt.Printf("map[%d]=%d\n", i, v)
    }
}

输出:

map[4]=24
map[14]=87178291200
map[17]=355687428096000
map[16]=20922789888000
map[19]=121645100408832000
map[9]=362880
map[1]=1
map[3]=6
map[8]=40320
map[15]=1307674368000
map[20]=2432902008176640000
map[18]=6402373705728000
map[11]=39916800
map[7]=5040
map[12]=479001600
map[5]=120
map[2]=2
map[6]=720
map[13]=6227020800
map[10]=3628800

单看这个 gomaxprocs(1) 则问题消失,合理猜测应该是并行问题(此处有 rob pike 的名言)。
即使结果是 map,也没必要多个 goroutine 同时操作同一个 map,引入 channel 在主 goroutine 里接收各个 goroutine 的计算结果存到最终 map;不然只能加锁解决了

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏