Golang for 循环中使用goroutine
最近在开发过程中遇到问题,追踪了很久后发现是golang的经典问题,在for循环中使用了goroutine,在goroutine中使用了for循环的参数。
问题现象:
在使用rabbitmq进行数据传递时,发送端在一次循环中发送了8000条id不同的数据到rabbitmq的队列中,接收端监听该队列并从rabbitmq中取数据。接收到的数据在程序中处理后写入数据库,结果发现数据中并没有写入8000条数据。最后定位原因为:在接收数据时在for循环中使用go协程,导致同时收到两条数据时,协程都是使用的后一条数据,入库因为是同一条数据,导致主键重复,插入失败,所以数据库中没有8000条数据。错误代码大致如下:
1 2 3 4 5 | for d := range msgs { go func() { handler(d) }() } |
用一个简单的程序模拟该错误为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package main import ( "fmt" "time" ) func main() { for i := 0; i < 10; i++ { go func() { fmt.Println(i) }() } time.Sleep(2 * time.Second) } |
输出为:
7
10
10
10
10
10
10
10
10
10
问题解析:
闭包go协程里面引用的是变量i的地址;所有的go协程启动后等待调用,在上面的协程中,部分协程很可能在for循环完成之后才被调用,所以输出结果很多都是10;正常输出最多到9哦
解决方法一
通过参数传递数据到协程
1 2 3 4 5 | for i := 0; i < 10; i++ { go func(data int) { fmt.Println(data) }(i) } |
解决方法二
在for循环中加一个临时变量tmp,每次将i的值赋值给tmp,然后将tmp通过参数传进协程。
此方法可以解决不能通过参数传递数据的情况(某些第三方库不能传参数)
1 2 3 4 5 6 | for i := 0; i < 10; i++ { tmp := i go func(data int) { fmt.Println(data) }(tmp) } |
产生上述问题的本质是,golang的for循环会使用同一个变量来存储迭代过程中的临时变量,在将该变量传递给goroutine时,goroutine得到的是该变量的地址,又由于goroutine的启动与调度机制有关,可能for循环执行完后,goroutine才开始调度,所以导致多个goroutine访问的是同一个数据。
相关推荐
zhaobig 2020-08-17
xiaonamylove 2020-08-16
CloudXli 2020-08-14
LowisLucifer 2020-08-09
yawei 2020-07-06
zlfing 2020-07-07
徐建岗网络管理 2020-06-09
LowisLucifer 2020-06-05
JamesRayMurphy 2020-05-31
Jieen 2020-05-18
zyazky 2020-05-17
sdwylry 2020-05-14
sucheng 2020-05-09
sdwylry 2020-04-27
tengyuan 2020-04-25
huakai 2020-02-25
Ericbig 2020-02-25
yunfenglee 2020-02-02