• golang 循环变量


    下面例子:

    package main
    
    import "fmt"
    
    func test1() {
    
        a1 := []int{1, 2, 3}
        a2 := make([]*int, len(a1))
    
        for i, v := range a1 {
            a2[i] = &v
        }
        fmt.Println("值:", *a2[0], *a2[1], *a2[2])
    
        fmt.Println("地址:", a2[0], a2[1], a2[2])
    }
    func main() {
    
        test1()
    }

    输出:

    值: 3 3 3
    地址: 0xc000016098 0xc000016098 0xc000016098
     
    原因:,range在遍历值类型时,其中的v是一个局部变量,只会声明初始化一次,之后每次循环时重新赋值覆盖前面的,所以给a2[i]赋值的时候其实都是同一个地址&v,而v最终的值为a1最后一个元素的值,也就是3。
    正确做法:
    a2[i]赋值时传递原始指针,即a2[i] = &a1[i]
    ②创建临时变量t := va2[i] = &t
    ③闭包(与②原理一样),func(v int) { a2[i] = &v }(v)
     
    更为隐秘的:
    func main() {
    
        var out [][]int
    
        for _, i := range [][1]int{{1}, {2}, {3}} {
    
            out = append(out, i[:])
    
        }
    
        fmt.Println("Values:", out)}// 输出结果// [[3] [3] [3]]


    原理也是一样的,不论遍历多少次,i[:]总是被本次遍历的值所覆盖

    场景二,在循环体内使用goroutines

    func main() {
    
        values := []int{1, 2, 3}
    
        wg := sync.WaitGroup{}
    
        for _, val := range values {
    
            wg.Add(1)
    
            go func() {
    
                fmt.Println(val)
    
                wg.Done()
    
            }()
    
        }
    
        wg.Wait()}// 输出结果// 3// 3// 3
    分析
    
    对于主协程来讲,循环是很快就跑完的,而这个时候各个协程可能才开始跑,此时val的值已经遍历到最后一个了,所以各协程都输出了3。(如果遍历数据庞大,主协程遍历耗时较久的话,goroutine的输出会根据当时候的val的值,所以每次的输出结果不一定相同的。)
    
    解决办法
    ①使用临时变量
    
    
    for _, val := range values {
    
        wg.Add(1)
    
        val := val    go func() {
    
            fmt.Println(val)
    
            wg.Done()
    
        }()}
    
    ②使用闭包
    
    
    for _, val := range values {
    
        wg.Add(1)
    
        go func(val int) {
    
            fmt.Println(val)
    
            wg.Done()
    
        }(val)}
     

    上面的错误代码vscode插件会提示:loop variable xx  captured by func literal loopclosure

     

    协程引用循环变量的坑
    循环体中启动协程异步执行,这个时候就容易出现问题了,比如下面这样一段代码就会出现我们不期望的结果。

    for i := 0; i < 10; i++ {
    go func() {
    fmt.Println(i)
    } ()
    }
    我们期望他能乱序输出0到9这几个数,但是他的执行结果并非如此。实际的执行结果如下:

    7
    10
    10
    10
    10
    10
    10
    10
    10
    7
    可以看到他的执行结果大家基本都输出10。其实原因也很容易解释:

    主协程的循环很快就跑完了,而各个协程才开始跑,此时i的值已经是10了,所以各协程都输出了10。(输出7的两个协程,在开始输出的时候主协程的i值刚好是7,这个结果每次运行输出都不一样)

     
     

    参考:https://www.cnblogs.com/wscsq789/p/15388804.html

  • 相关阅读:
    Dynamics CRM 2011/2013 通过Javascript给lookup字段赋值
    shell重定向(大于号,小于号,左右,2>&1,&)
    Dynamics CRM2011 同一个FORM表单同一个字段可以摆放多次
    词的向量表示
    机器翻译领域的新突破
    Dynamics CRM2011 隐藏sub-grid 新建项和添加现有项按钮
    sed常用方法与命令
    Dynamics CRM Odata QueryUrl中的SetName问题
    hive发杂数据结构的使用,struct,array,map
    maven 经常使用命令
  • 原文地址:https://www.cnblogs.com/youxin/p/16121422.html
Copyright © 2020-2023  润新知