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 语言的编程技巧之变量
32 0
|
2月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
80 4
|
2月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
46 4
Golang语言goroutine协程篇
|
2月前
|
Go
Golang语言基础之标识符和变量定义
这篇文章详细介绍了Go语言中标识符和变量的定义、命名规则、关键字、变量类型、声明方式、作用域等基础知识。
21 3
|
3月前
|
NoSQL Unix 编译器
Golang协程goroutine的调度与状态变迁分析
文章深入分析了Golang中goroutine的调度和状态变迁,包括Grunnable、Gwaiting、Grunning和Gsyscall等状态,以及它们之间的转换条件和原理,帮助理解Go调度器的内部机制。
44 0
|
6月前
|
Go 开发者
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
【4月更文挑战第21天】本文介绍了Go语言中的流程控制语句,包括`if`、`switch`和`for`循环。`if`语句支持简洁的语法和初始化语句,但需注意比较运算符的使用。`switch`语句提供多分支匹配,可省略`break`,同时支持不带表达式的形式。`for`循环有多种形式,如基本循环和`for-range`遍历,遍历时修改原集合可能导致未定义行为。理解并避免易错点能提高代码质量和稳定性。通过实践代码示例,可以更好地掌握Go语言的流程控制。
50 3
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
|
6月前
|
监控 负载均衡 算法
Golang深入浅出之-Go语言中的协程池设计与实现
【5月更文挑战第3天】本文探讨了Go语言中的协程池设计,用于管理goroutine并优化并发性能。协程池通过限制同时运行的goroutine数量防止资源耗尽,包括任务队列和工作协程两部分。基本实现思路涉及使用channel作为任务队列,固定数量的工作协程处理任务。文章还列举了一个简单的协程池实现示例,并讨论了常见问题如任务队列溢出、协程泄露和任务调度不均,提出了解决方案。通过合理设置缓冲区大小、确保资源释放、优化任务调度以及监控与调试,可以避免这些问题,提升系统性能和稳定性。
197 6
|
6月前
|
存储 程序员 Go
第三章 Golang变量
第三章 Golang变量
51 2
|
Go
100天精通Golang(基础入门篇)——第9天:Go语言程序的循环语句
100天精通Golang(基础入门篇)——第9天:Go语言程序的循环语句
43 0
|
存储 编译器 Go
100天精通Golang(基础入门篇)——第4天: Go语言中的变量与常量详解:声明、赋值和使用
100天精通Golang(基础入门篇)——第4天: Go语言中的变量与常量详解:声明、赋值和使用
71 0