【Go】goroutine并发常见的变量覆盖案例


越过山丘
遇见六十岁的我
拄着一根白手杖
在听鸟儿歌唱
我问他幸福与否
他笑着摆了摆手
在他身边围绕着一群
当年流放归来的朋友
他说你不必挽留
爱是一个人的等候
等到房顶开出了花
这里就是天下
总有人幸福白头
总有人哭着分手
无论相遇还是不相遇
都是献给岁月的序曲
                     🎵 杨宗纬《越过山丘》


在 Go 语言中,一个常见的变量覆盖案例涉及到闭包和并发。当你在一个循环中启动多个并发的 goroutine,并且这些 goroutine 引用了循环的迭代变量时,就有可能发生覆盖。这是因为闭包中的变量是通过引用捕获的,而不是通过值。如果闭包在下一次迭代之前没有执行,那么它使用的变量值可能就是迭代变量的最新值。
下面是一个简单的示例,说明了如何在 Go 语言中发生这种覆盖:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			fmt.Println(i) // 此处的 i 可能在 goroutine 执行时被覆盖
			wg.Done()
		}()
	}
	// 给 goroutines 时间启动
	time.Sleep(time.Second)
	wg.Wait()
}

在上面的代码中,我们启动了 5 个 goroutine,每个都打印变量 i 的值。但是,因为这些 goroutine 可能在 for 循环结束后才开始执行,所以它们都可能打印出同一个数字(通常是最后一个迭代的数字,即 4),而不是每个 goroutine 打印出其对应迭代的数字。

在 Go 中,goroutine 是并发执行的,这意味着它们是在程序的其他部分独立运行的轻量级线程。当你在 for 循环中使用 go 关键字启动一个 goroutine 时,Go 会计划在未来的某个时间点运行这个 goroutine。这个确切的时间点是由 Go 运行时的调度器决定的,它处理所有的并发任务并决定它们的执行顺序。

由于 goroutine 的启动是非阻塞的,for 循环并不会等待每个 goroutine 启动或完成。循环会立即继续执行,进入下一次迭代,最终在所有 goroutine 都被计划后很快完成。

因此,如果 goroutine 内部使用了循环变量,例如上面例子中的 i,并且 goroutine 的执行被推迟到循环完成之后,所有的 goroutine 可能会看到 i 的最终值,因为它们都引用了同一个变量 i。

在实践中,这意味着在循环完成之前,goroutine 可能没有机会开始执行。特别是在循环迅速执行并且没有显式的同步机制(如 sync.WaitGroup)来等待每个 goroutine 的完成的情况下,很可能所有 goroutine 都将在循环结束后才开始执行。

在处理 goroutine 和循环变量时,最佳实践是将每次迭代的变量值传递给 goroutine,以避免意外捕获循环变量的最终值。这可以通过将迭代变量作为参数传递给 goroutine 的匿名函数来实现,从而为每个 goroutine 创建一个变量的副本。这样,即使 goroutine 在循环结束后开始执行,它也将具有正确的迭代值。

为了避免这个问题,你可以在每次迭代中创建一个循环作用域内的变量副本,并将其传递给闭包:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(localI int) { // 使用局部变量 localI
			fmt.Println(localI) // 打印局部变量,而不是循环变量 i
			wg.Done()
		}(i) // 传递当前迭代的 i 的值
	}
	// 给 goroutines 时间启动
	time.Sleep(time.Second)
	wg.Wait()
}

这个修改后的版本为每个迭代创建了一个 localI 变量的副本,并将其传递给每个 goroutine。这样,每个 goroutine 有它自己的 localI 副本,而且不会被其他迭代覆盖。

相关推荐

  1. 【Go】goroutine并发常见变量覆盖案例

    2024-03-31 14:28:02       41 阅读
  2. 常见并联谐振应用案例

    2024-03-31 14:28:02       56 阅读
  3. 并发编程中常见设计模式

    2024-03-31 14:28:02       44 阅读
  4. JPA乐观锁实现并发执行SQL案例

    2024-03-31 14:28:02       48 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-31 14:28:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-31 14:28:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-31 14:28:02       82 阅读
  4. Python语言-面向对象

    2024-03-31 14:28:02       91 阅读

热门阅读

  1. Vue的侦听方法和生命周期

    2024-03-31 14:28:02       41 阅读
  2. Viso的使用

    2024-03-31 14:28:02       57 阅读
  3. LeetCode 84. 柱状图中最大的矩形

    2024-03-31 14:28:02       34 阅读
  4. 【BlossomRPC】一个完整的含源码和文档的RPC项目

    2024-03-31 14:28:02       35 阅读
  5. 补关于zip安装mysql-8.0版本问题

    2024-03-31 14:28:02       39 阅读
  6. cephadm安装reef版本ceph集群

    2024-03-31 14:28:02       39 阅读
  7. PyTorch-----torch.flatten()函数

    2024-03-31 14:28:02       35 阅读
  8. js关于字符串的方法

    2024-03-31 14:28:02       40 阅读
  9. 2024年保安员职业资格考试真题题库

    2024-03-31 14:28:02       38 阅读