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


相关文章
|
7月前
|
安全 Go PHP
Golang 语言的编程技巧之变量
Golang 语言的编程技巧之变量
14 0
|
2天前
|
监控 负载均衡 算法
Golang深入浅出之-Go语言中的协程池设计与实现
【5月更文挑战第3天】本文探讨了Go语言中的协程池设计,用于管理goroutine并优化并发性能。协程池通过限制同时运行的goroutine数量防止资源耗尽,包括任务队列和工作协程两部分。基本实现思路涉及使用channel作为任务队列,固定数量的工作协程处理任务。文章还列举了一个简单的协程池实现示例,并讨论了常见问题如任务队列溢出、协程泄露和任务调度不均,提出了解决方案。通过合理设置缓冲区大小、确保资源释放、优化任务调度以及监控与调试,可以避免这些问题,提升系统性能和稳定性。
28 6
|
2天前
|
Go 开发者
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
【4月更文挑战第21天】本文介绍了Go语言中的流程控制语句,包括`if`、`switch`和`for`循环。`if`语句支持简洁的语法和初始化语句,但需注意比较运算符的使用。`switch`语句提供多分支匹配,可省略`break`,同时支持不带表达式的形式。`for`循环有多种形式,如基本循环和`for-range`遍历,遍历时修改原集合可能导致未定义行为。理解并避免易错点能提高代码质量和稳定性。通过实践代码示例,可以更好地掌握Go语言的流程控制。
20 3
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
|
7月前
|
存储 编译器 Go
100天精通Golang(基础入门篇)——第4天: Go语言中的变量与常量详解:声明、赋值和使用
100天精通Golang(基础入门篇)——第4天: Go语言中的变量与常量详解:声明、赋值和使用
46 0
|
8月前
|
并行计算 安全 Go
Golang协程:并发编程的利器
在Go编程语言中,协程(goroutine)是一种轻量级的线程,用于实现并发编程。与传统的线程相比,协程更加高效、简洁,并且易于使用。本篇博客将深入探讨Golang中的协程及其优势。
62 0
|
10月前
|
Go
02-Golang-变量定义和使用
02-Golang-变量定义和使用
|
10月前
|
Go
golang中for循环的遍历特点、数据类型和string转换
golang中for循环的遍历特点、数据类型和string转换
|
10月前
|
存储 程序员 Go
Golang-变量和常量
Golang 中的变量和常量
42 0
|
10月前
|
JavaScript 前端开发 Go
正确使用 Golang 循环创建新切片的方式
正确使用 Golang 循环创建新切片的方式
|
11月前
|
Linux Go
学习golang(12) 初探:协程(3)多个chan之select选择器
学习golang(12) 初探:协程(3)多个chan之select选择器
174 0