golang 循环创建新协程,发现每次使用的循环变量都一样,都是最后一个

简介: golang 循环创建新协程,发现每次使用的循环变量都一样,都是最后一个

问题描述

循环创建新协程,发现每次使用的循环变量都一样,都是最后一个

package main
import (
  "fmt"
  "time"
)
func main() {
  type Student struct {
    Name string
    Age int
  }
  studentList := []*Student{
    {
      Name: "张三",
      Age: 13,
    },
    {
      Name: "李四",
      Age: 13,
    },
    {
      Name: "王五",
      Age: 13,
    },
  }
  for idx, stu := range studentList {
    go func() {
      fmt.Printf("%v: %v\n", idx, stu)
    }()
  }
  time.Sleep(3 * time.Second)
}

输出

2: &{王五 13}
2: &{王五 13}
2: &{王五 13}

可以发现输出的下标都是 2,对象都是王五,这是因为 idx 和 stu 每次循环时都是同一个变量,每次用新值覆盖旧值,子协程内部的 idx 和 stu 也都是跟主线程里的idx 和 stu是同一个变量,如果主线程中idx和stu发生变化,子协程里的两个变量也会发生变化。循环速度太快,循环了三次才开始执行子协程代码,导致子协程执行时idx和 stu已经被覆盖成最新的了,所以三次打印出来的同样的值。

解决方案:

在每次循环时重新定义变量,这样每个协程使用的就是循环内部的变量,而不是外层循环时共享的变量1456655-20220915200038516-936108090.png

如果不好理解,可以改成下面这样,上面的只不过重新定义的两个变量名字跟外层循环里的变量名字一样

for idx, stu := range studentList {
    index, student := idx, stu
    go func() {
      fmt.Printf("%v: %v\n", index, student)
    }()
  }

输出

1: &{李四 13}
0: &{张三 13}
2: &{王五 13}

bye the way 1

下面这种企图通过给子协程传参的方式是无法解决上面的问题的,因为本质上传给协程的都是同两个变量,只不过变量的值变化了三次,等开始执行协程代码时,协程内部的遍历值已经被覆盖成最新修改的值了

1456655-20220915200624798-316807540.png输出

2: &{王五 13}
2: &{王五 13}
2: &{王五 13}

by the way 2

即使 range 循环时,每个元素的类型是值类型,不是引用类型,那他们也同样复用的是同个变量,就是每次循环都是修改了循环变量的值,而不是重新创建了一个循环变量。比如下面的代码,循环变量 element 是 int 类型,但是所有协程打印出来还是都是最后一次被赋的值 4。

func main()  {
  arr := []int{1, 2, 3, 4}
  for _, element := range arr{
    go func() {
      fmt.Println(element)
    }()
  }
  time.Sleep(time.Second * 3)
}

输出结果

4
4
4
4

解决方案也是在开始使用循环变量之前,重新定义一个变量,新协程使用新变量而不是循环变量

1456655-20221028105651737-1990583163.png

输出结果

4
1
3
2


相关文章
|
安全 Go PHP
Golang 语言的编程技巧之变量
Golang 语言的编程技巧之变量
75 0
|
1月前
|
Go 调度 Python
Golang协程和Python协程用法上的那些“不一样”
本文对比了 Python 和 Go 语言中协程的区别,重点分析了调度机制和执行方式的不同。Go 的协程(goroutine)由运行时自动调度,启动后立即执行;而 Python 协程需通过 await 显式调度,依赖事件循环。文中通过代码示例展示了两种协程的实际运行效果。
101 7
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
244 4
|
10月前
|
存储 安全 测试技术
GoLang协程Goroutiney原理与GMP模型详解
本文详细介绍了Go语言中的Goroutine及其背后的GMP模型。Goroutine是Go语言中的一种轻量级线程,由Go运行时管理,支持高效的并发编程。文章讲解了Goroutine的创建、调度、上下文切换和栈管理等核心机制,并通过示例代码展示了如何使用Goroutine。GMP模型(Goroutine、Processor、Machine)是Go运行时调度Goroutine的基础,通过合理的调度策略,实现了高并发和高性能的程序执行。
589 30
|
10月前
|
Go 计算机视觉
在Golang高并发环境中如何进行协程同步?
在此示例中,使用互斥锁来保护对共享计数器变量 c 的访问,确保并发的 HTTP 请求不会产生数据竞争。
174 3
|
10月前
|
负载均衡 算法 Go
GoLang协程Goroutiney原理与GMP模型详解
【11月更文挑战第4天】Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时管理,创建和销毁开销小,适合高并发场景。其调度采用非抢占式和协作式多任务处理结合的方式。GMP 模型包括 G(Goroutine)、M(系统线程)和 P(逻辑处理器),通过工作窃取算法实现负载均衡,确保高效利用系统资源。
202 3
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
533 4
Golang语言goroutine协程篇
|
Go
Golang语言基础之标识符和变量定义
这篇文章详细介绍了Go语言中标识符和变量的定义、命名规则、关键字、变量类型、声明方式、作用域等基础知识。
119 3
|
NoSQL Unix 编译器
Golang协程goroutine的调度与状态变迁分析
文章深入分析了Golang中goroutine的调度和状态变迁,包括Grunnable、Gwaiting、Grunning和Gsyscall等状态,以及它们之间的转换条件和原理,帮助理解Go调度器的内部机制。
198 0
|
Go 开发者
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
【4月更文挑战第21天】本文介绍了Go语言中的流程控制语句,包括`if`、`switch`和`for`循环。`if`语句支持简洁的语法和初始化语句,但需注意比较运算符的使用。`switch`语句提供多分支匹配,可省略`break`,同时支持不带表达式的形式。`for`循环有多种形式,如基本循环和`for-range`遍历,遍历时修改原集合可能导致未定义行为。理解并避免易错点能提高代码质量和稳定性。通过实践代码示例,可以更好地掌握Go语言的流程控制。
116 3
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解

推荐镜像

更多