goroutine使用了外面的变量,造成闭包问题。怎么解决呢?

发布于 2022-09-11 18:20:49 字数 1270 浏览 24 评论 0

问题描述

我有这样一个程序,他从一个items列表中读取items,然后,打印一下这个item,交个一个channel

// 从out队列接收数据
        for _,item := range result.Items {
            fmt.Println("got item: ",item)
            go func() {
                c.ItemChan <- item
            }()
        }

在另一个地方,起了一个goroutine,来接收

    go func() {
        count := 0
        for {
            item := <- out
            count++
            fmt.Printf("获取到的item:%d,当前saver计数count是:%d\n",item,count)
        }
    }()

很简单的一个东西,但是发现。写入是打印的item没有重复,但读取的channel,打印有重复。
最后发现应该是写入是,channel阻塞,而外层循环还在继续,导致item更新了,然后在写入channel的已经是几个相同的item了

问题出现的环境背景及自己尝试过哪些方法

我尝试给写入添加sleep,这样只要确保这个被消费了,然后在写入,就没问题

        for _,item := range result.Items {
            time.Sleep(time.Millisecond * 100)
            fmt.Println("got item: ",item)
            go func() {
                c.ItemChan <- item
            }()
        }

但显然,这个不符合需求。

那么,我的问题是,我有一个列表要不断读取,通过goroutine塞到一个channel里面,但这样会造成闭包问题,该怎么解决呢?

也许,将写入改成一个大的goroutine是个好办法?
goroutine包裹整个for循环,然后,此时在写入channel就是阻塞的了

相关代码

// 请把代码文本粘贴到下方(请勿用图片代替代码)

你期待的结果是什么?实际看到的错误信息又是什么?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

心是晴朗的。 2022-09-18 18:20:50

你第一段的写法,如果有装代码检查之类的插件就会有警告,因为循环可能会在goroutine执行前改变item的值。

直接把item通过参数方式式应该可以,但要注意,写入的goroutine的执行顺序并不是按for range的顺序的。

风柔一江水 2022-09-18 18:20:50

与闭包无关,是go造成的。
for循环是同步的,所以会造成循环结束之后,协程才启动,此时拿到的值全部是for循环的最后一次值

德意的啸 2022-09-18 18:20:50

你打印写入的地方有问题。

for _,item := range result.Items {
            //fmt.Println("got item: ",item)//把这个放 go func()里面
            go func() {
                fmt.Println("got item: ",item)
                c.ItemChan <- item
            }()
        }

这样你打印的就和接收到的数据是一样的了。
因为go func 并不能保证立即执行,在它还没有执行的时候,item值已经被for循环改变了

我有个疑问,你在写入的地方起goroutine是为了for循环不被写入阻塞吗?如果不想因为写入被阻塞,可以给channel定义一个缓冲。按照例子的,缓冲数定义为items的数量即可

老娘不死你永远是小三 2022-09-18 18:20:50

两选一:

  1. 用临时变量把range遍历切片的值先接下来,确保每次写入channel的item是不同的
  2. 用带缓冲区的channel

第一种方法:

for _,item := range result.Items {
    temp := item
    fmt.Println("got item: ",temp )
    go func() {
        c.ItemChan <- temp 
    }()
}
绅刃 2022-09-18 18:20:49

自己思考了一下。其实,既然是闭包。那么,只要将item作为参数,传递给这个循环goroutine,就可以了
他作为形参,自然不会被修改

// 从out队列接收数据
        for _,item := range result.Items {
            fmt.Println("got item: ",item)
            go func(rec interface{}) {
                c.ItemChan <- rec
            }(item)
        }

带缓冲的channel,我认为不是一个好的办法。因为,缓冲终归会满。

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文