Go 1.22 版本发布,这个大坑终于给填上了~

大家好啊,我是码财同行。

今年 2月6日,Go 官方发布了最新的 Go1.22 版本。在这个版本中,做了一些语言上的修改,填上了一个大坑:

在 Golang 的开发中,有一个很经典的有关 for 循环的 陷阱,基本每一个学习 golang 语言的都要掉进去一次。

话不多说,先来看一段代码:

golang 复制代码
package main

import (
 "fmt"
)

func main() {
    done := make(chan bool)

    // 遍历 slice 中的所有元素,分别开协程对其进行一段逻辑操作
    // 这里,用打印元素来代表一段逻辑
    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // 等所有协程执行完
    for _ = range values {
        <-done
    }
}

在这里,我们的预期结果是依次打印 a、b、c,可实际结果让人大跌眼镜:

bash 复制代码
$ ./gote
c
c
c

什么原因?

这主要是因为 Golang 编译器在处理 for 循环时候的方法问题。

在 Golang 中,经典的 for 循环是这个样子的, 和 C 语言的 for 循环类似:

golang 复制代码
// Like a C for 
for init; condition; post { 
    // ...
}

如果要遍历一些结构如 slice、map、string、channel 的时候,编译器支持 range 这种形式的语法糖

golang 复制代码
for i, v := range a {
    // ...
}

这段代码在被编译的时候,这种 range 形式的语法糖会被改写成上面提到的 "经典的 for 循环" 形式。

具体是改写成什么样子呢?

我们看下被改写后的代码:

golang 复制代码
ha := a //拷贝一份出来做遍历,而不是在原数据结构上遍历

hv1 := 0 
hn := len(ha) //先计算结构的长度,拿到遍历次数

v1 := hv1 // v1 对应上面 range 形式中的变量 i
v2 := nil // v2 对应上面 range 形式中的 v

// 注意:这里 v1, v2 都是是公共的全局值,而不是每次循环申请

// 经典形式的 for
for ; hv1 < hn; hv1++ { 
    tmp := ha[hv1] 
    
    // 值被赋值给全局变量 v1,v2
    v1, v2 = hv1, tmp
    ... 
}

从上面的代码注释中,我们可以看到:range 变量中的 v 是个公共变量,而不是遍历一次申请一个。

回到文章开始时候的例子:

golang 复制代码
values := []string{"a", "b", "c"}
for _, v := range values {
    go func() {
        fmt.Println(v)
        done <- true
    }()
}

上面代码中的变量 v 如果是个公共变量,那每个协程在执行的时候,读取的这个闭包变量就是同一个,公共的变量。

实际执行的时候,当前协程中的 slice(变量 values)很快被遍历完,那 v 就指向了最后一个元素 c。

此时,新开启的几个协程(在这个例子中是3个),因为调度的原因,很可能比当前协程执行的慢,当 v 的值被赋值成最后一个元素 c 的时候,所有协程打印的都是这个公共遍历 v 的值,也是就 c 了。

这可真是一个大坑~

好在,如果学习了一段时间的 golang,多多少少都会知道这个坑,大家也习惯了绕着走,老老实实把变量 v 通过协程的函数参数传进去。

现在 Go 1.22 发布,总算把这个大坑填上了。如果你的 Go 版本是 1.22 的,测试一下,可以发现之前的一段代码可以正常输出期望的结果 a, b, c 了。

好了,看了这么多,一定很费脑力吧。来个笑话放松一下 :)

【笑话一则】一条鱼往深海里游,游着游着它就哭了,因为压力好大。

感谢您花时间阅读这篇文章!如果觉得有趣或有收获,请来个关注、评论、点赞吧,您的鼓励是我持续创作的动力,蟹蟹!

相关推荐
笃行3501 小时前
从零开始:SpringBoot + MyBatis + KingbaseES 实现CRUD操作(超详细入门指南)
后端
该用户已不存在2 小时前
这几款Rust工具,开发体验直线上升
前端·后端·rust
用户8356290780512 小时前
C# 从 PDF 提取图片教程
后端·c#
L2ncE2 小时前
高并发场景数据与一致性的简单思考
java·后端·架构
水涵幽树2 小时前
MySQL 时间筛选避坑指南:为什么格式化字符串比较会出错?
数据库·后端·sql·mysql·database
岁忧3 小时前
(nice!!!)(LeetCode 每日一题) 1277. 统计全为 1 的正方形子矩阵 (动态规划)
java·c++·算法·leetcode·矩阵·go·动态规划
ERP老兵_冷溪虎山3 小时前
从ASCII到Unicode:"国际正则"|"表达式"跨国界实战指南(附四大语言支持对比+中医HIS类比映射表)
后端·面试
HyggeBest3 小时前
Golang 并发原语 Sync Cond
后端·架构·go
老张聊数据集成3 小时前
数据建模怎么做?一文讲清数据建模全流程
后端
颜如玉3 小时前
Kernel bypass技术遥望
后端·性能优化·操作系统