zoukankan      html  css  js  c++  java
  • golang循环变量绑定

    首先上代码

    题目一:

    package main
    import (
        "fmt"
        "time"
    )
    
    func Process1(tasks []string) {
        for _, task := range tasks {
            // 启动协程并发处理任务
            go func() {
                fmt.Printf("Worker start process task: %s
    ", task)
            }()
        }
    }
    
    
    func main() {
        tasks := []string{"1", "2", "3", "4", "5"}
        Process1(tasks)
        time.Sleep(2 * time.Second)
    }
    
    #连续运行两次,输出结构不一致:
    kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go 
    Worker start process task: 5
    Worker start process task: 5
    Worker start process task: 5
    Worker start process task: 5
    Worker start process task: 5
    kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go
    Worker start process task: 4
    Worker start process task: 5
    Worker start process task: 5
    Worker start process task: 5
    Worker start process task: 5

    再运行下面代码

    题目二:

    package main
    import (
        "fmt"
        "time"
    )
    
    func Process1(tasks []string) {
        for _, task := range tasks {
            // 启动协程并发处理任务
            go func() {
                fmt.Printf("Worker start process task: %s
    ", task)
            }()
        }
    }
    
    func Process2(tasks []string) {
        for _, task := range tasks {
            // 启动协程并发处理任务
            go func(t string) {
                fmt.Printf("Worker start process task: %s
    ", t)
            }(task)
        }
    }
    
    func main() {
        tasks := []string{"1", "2", "3", "4", "5"}
        Process2(tasks)
        time.Sleep(2 * time.Second)
    }
    
    #除顺序不一致,运行结果可以达到预期:
    kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go
    Worker start process task: 2
    Worker start process task: 1
    Worker start process task: 5
    Worker start process task: 4
    Worker start process task: 3
    kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go
    Worker start process task: 1
    Worker start process task: 3
    Worker start process task: 4
    Worker start process task: 5
    Worker start process task: 2

    题目三:

    package main
    import (
        "fmt"
        "time"
    )
    
    func Process1(tasks []string) {
        for _, task := range tasks {
            // 启动协程并发处理任务
            go func() {
                fmt.Printf("Worker start process task: %s
    ", task)
            }()
        }
    }
    func Process2(tasks []string) {
        for _, task := range tasks {
            // 启动协程并发处理任务
            go func(t string) {
                fmt.Printf("Worker start process task: %s
    ", t)
            }(task)
        }
    }
    
    func Process3(tasks []string){
        for _,task := range tasks {
            tasknew := task
            go func() {
                fmt.Printf("Worker start process task:%s
    ", tasknew)
            }()
        }
    }
    
    func main() {
        tasks := []string{"1", "2", "3", "4", "5"}
        Process3(tasks)
        time.Sleep(2 * time.Second)
    }
    
    #运行输出如下,结果也能达到预期:
    kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go
    Worker start process task:5
    Worker start process task:2
    Worker start process task:4
    Worker start process task:1
    Worker start process task:3
    kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go
    Worker start process task:3
    Worker start process task:1
    Worker start process task:2
    Worker start process task:5
    Worker start process task:4

    以下分析摘自网上资料:

    原理剖析

    上述问题,有个共同点就是都引用了循环变量。即在for index, value := range xxx语句中,
    indexvalue便是循环变量。不同点是循环变量的使用方式,有的是直接在协程中引用(题目一),有的作为参数传递(题目二)。

    回答以上问题,记住以下两点即可。

    循环变量是易变的

    首先,循环变量实际上只是一个普通的变量。

    语句for index, value := range xxx中,每次循环index和value都会被重新赋值(并非生成新的变量)。

    如果循环体中会启动协程(并且协程会使用循环变量),就需要格外注意了,因为很可能循环结束后协程才开始执行,
    此时,所有协程使用的循环变量有可能已被改写。(是否会改写取决于引用循环变量的方式)

    循环变量需要绑定

    在题目一中,协程函数体中引用了循环变量task,协程从被创建到被调度执行期间循环变量极有可能被改写,
    这种情况下,我们称之为变量没有绑定。
    所以,题目一打印结果是混乱的。很有可能(随机)所有协程执行的task都是列表中的最后一个task。

    在题目二中,协程函数体中并没有直接引用循环变量task,而是使用的参数。而在创建协程时,循环变量task
    作为函数参数传递给了协程。参数传递的过程实际上也生成了新的变量,也即间接完成了绑定。
    所以,题目二实际上是没有问题的。

    在题目三中,通过  tasknew := task 显式地绑定,每次循环会生成一个新的变量

    总结

    简单点来说

    • 如果循环体没有并发出现,则引用循环变量一般不会出现问题;
    • 如果循环体有并发,则根据引用循环变量的位置不同而有所区别
      • 通过参数完成绑定,则一般没有问题;
      • 函数体中引用,则需要显式地绑定
  • 相关阅读:
    eIQ WSL下工具及环境配置
    WSL配置高翔vslam环境配置流水账
    机器学习原理/模型/应用
    Spring+Quartz(定时任务)
    vim常用操作
    Linux使用ssh公钥实现免密码登录Linux
    svn常用操作
    Jquery Html方法失效的问题
    运算符&&与||的用法
    CSS强制不换行[转帖]
  • 原文地址:https://www.cnblogs.com/wscsq789/p/15388804.html
Copyright © 2011-2022 走看看