zoukankan      html  css  js  c++  java
  • 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)
    }

    转载: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/

    产生上述问题的本质是,golang的for循环会使用同一个变量来存储迭代过程中的临时变量,在将该变量传递给goroutine时,goroutine得到的是该变量的地址,又由于goroutine的启动与调度机制有关,可能for循环执行完后,goroutine才开始调度,所以导致多个goroutine访问的是同一个数据。

  • 相关阅读:
    andorid UI事件 监听器
    12小时进制的时间输出的编辑代码
    Java运算符
    运算符的优先级
    UTF-8
    对ASCII的了解
    数组
    Java语法基础
    Java的跨平台
    指针的了解
  • 原文地址:https://www.cnblogs.com/leadership/p/12090849.html
Copyright © 2011-2022 走看看