6

记一次golang经典错误--for循环中的go协程调用

 2 years ago
source link: https://baorongquan.github.io/2018/05/06/%E8%AE%B0%E4%B8%80%E6%AC%A1golang%E7%BB%8F%E5%85%B8%E9%94%99%E8%AF%AF-for%E5%BE%AA%E7%8E%AF%E4%B8%AD%E7%9A%84go%E5%8D%8F%E7%A8%8B%E8%B0%83%E7%94%A8/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

记一次golang经典错误--for循环中的go协程调用

发表于 2018-05-06

| 分类于 programming

| 0 Comments

| 浏览 次

最近在开发过程中遇到问题,追踪了很久后发现是golang的经典问题,在for循环中使用了goroutine,在goroutine中使用了for循环的参数。

问题现象:

在使用rabbitmq进行数据传递时,发送端在一次循环中发送了8000条id不同的数据到rabbitmq的队列中,接收端监听该队列并从rabbitmq中取数据。接收到的数据在程序中处理后写入数据库,结果发现数据中并没有写入8000条数据。最后定位原因为:在接收数据时在for循环中使用go协程,导致同时收到两条数据时,协程都是使用的后一条数据,入库因为是同一条数据,导致主键重复,插入失败,所以数据库中没有8000条数据。错误代码大致如下:

for d := range msgs {
go func() {
handler(d)

用一个简单的程序模拟该错误为:

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哦

解决方法一

通过参数传递数据到协程

for i := 0; i < 10; i++ {
go func(data int) {
fmt.Println(data)

解决方法二

在for循环中加一个临时变量tmp,每次将i的值赋值给tmp,然后将tmp通过参数传进协程。

此方法可以解决不能通过参数传递数据的情况(某些第三方库不能传参数)

for i := 0; i < 10; i++ {
tmp := i
go func(data int) {
fmt.Println(data)
}(tmp)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK