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 显式地绑定,每次循环会生成一个新的变量

    总结

    简单点来说

    • 如果循环体没有并发出现,则引用循环变量一般不会出现问题;
    • 如果循环体有并发,则根据引用循环变量的位置不同而有所区别
      • 通过参数完成绑定,则一般没有问题;
      • 函数体中引用,则需要显式地绑定
  • 相关阅读:
    Spring Cloud Hystrix Dashboard的使用 5.1.3
    Spring Cloud Hystrix 服务容错保护 5.1
    Spring Cloud Ribbon 客户端负载均衡 4.3
    Spring Cloud 如何实现服务间的调用 4.2.3
    hadoop3.1集成yarn ha
    hadoop3.1 hdfs的api使用
    hadoop3.1 ha高可用部署
    hadoop3.1 分布式集群部署
    hadoop3.1伪分布式部署
    KVM(八)使用 libvirt 迁移 QEMU/KVM 虚机和 Nova 虚机
  • 原文地址:https://www.cnblogs.com/wscsq789/p/15388804.html
Copyright © 2011-2022 走看看