V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
rockyou12
V2EX  ›  Go 编程语言

用 go 写 worker pool 的疑惑

  •  
  •   rockyou12 · 2016-09-05 15:37:39 +08:00 · 1909 次点击
    这是一个创建于 3030 天前的主题,其中的信息可能已经有所发展或是发生改变。

    研究用 go 控制并发数的时候发现一个example,就改造了下发现一个问题,源码

    package main
    
    import "time"
    
    func worker(id int, jobs <-chan int, results chan <- int) {
    	for j := range jobs {
    		time.Sleep(time.Second)
    		results <- j
    	}
    }
    
    func main() {
    	jobs := make(chan int, 100)
    	results := make(chan int, 100)
    	for w := 1; w <= 3; w++ {
    		go worker(w, jobs, results)
    	}
    
    	//发送 jobs
    	for j := 1; j <= 10000; j++ {
    		go func() {
    			jobs <- j
    		}()
    	}
    	//go func() {
    	//	for j := 1; j <= 10000; j++ {
    	//		jobs <- j
    	//	}
    	//}()
    
    	for {
    		select {
    		case r := <-results:
    			println("result:", r)
    		}
    	}
    
    }
    

    但输出缺有缺失,并且还有重复

    result: 14
    result: 15
    result: 8
    result: 15
    result: 15
    result: 15
    result: 15
    result: 15
    result: 15
    ...
    

    将源码中发送 jobs 的部分改为

    	go func() {
    		for j := 1; j <= 10000; j++ {
    			jobs <- j
    		}
    	}()
    

    结果又是正常的

    result: 3
    result: 2
    result: 1
    result: 5
    result: 4
    result: 6
    result: 9
    result: 8
    result: 7
    result: 10
    result: 12
    result: 11
    result: 14
    result: 15
    result: 13
    

    有谁知道原因吗?

    18 条回复    2016-09-06 14:11:25 +08:00
    fds
        1
    fds  
       2016-09-05 15:49:32 +08:00
    很明显 goroutine 执行的时候, j 的值变了呗。我猜可以存一下:
    for j := 1; j <= 10000; j++ {
    k: = j
    go func() {
    jobs <- k
    }()
    }
    fds
        2
    fds  
       2016-09-05 15:52:41 +08:00
    哦 k := j 前面代码的空格位置错了 ~~~~(>_<)~~~~
    cinhoo
        3
    cinhoo  
       2016-09-05 15:52:53 +08:00
    data race 呗
    shidenggui
        4
    shidenggui  
       2016-09-05 15:53:13 +08:00
    ```
    go func(j int) {
    jobs <- j
    }(j)
    ```

    或者

    ```
    j := j
    go func() {
    jobs <- j
    }()
    cinhoo
        5
    cinhoo  
       2016-09-05 15:57:49 +08:00
    go run -race 检查下
    suchj
        6
    suchj  
       2016-09-05 16:08:58 +08:00   ❤️ 1
    看一下闭包的相关知识吧,主要注意变量作用域这一块,就能明白这个结果了
    zts1993
        7
    zts1993  
       2016-09-05 16:18:48 +08:00
    //发送 jobs
    for j := 1; j <= 10000; j++ {
    go func(int j) {
    jobs <- j
    }(j)
    }


    go , defer 与闭包 变量作用域~~~ 补补基础咯
    rockyou12
        8
    rockyou12  
    OP
       2016-09-05 16:26:55 +08:00
    我自问自答了……楼上就 6 楼说到点子。这个是 go 语言匿名函数的一个天坑,参考 5.6.1
    https://docs.ruanjiadeng.com/gopl-zh/ch5/ch5-06.html
    0987363
        9
    0987363  
       2016-09-05 16:32:46 +08:00
    @rockyou12 4,7 楼也是对的,哈哈
    rockyou12
        10
    rockyou12  
    OP
       2016-09-05 16:34:44 +08:00
    @0987363 确实……(⊙﹏⊙)b
    go 这个坑真是藏得深,不知道设计的时候是怎么想的
    nino
        11
    nino  
       2016-09-05 17:15:41 +08:00
    这跟闭包关系不大,主要原因是 go func 是异步 的
    rockyou12
        12
    rockyou12  
    OP
       2016-09-05 17:46:24 +08:00
    @nino 还是有点关系吧,比如在 java7 里面,匿名内部类引用外部变量是要上 final 的, java8 里面把这个都简化了可以不写,但 go 就没有检查这些,让这个地雷默默的埋在那里。
    nino
        13
    nino  
       2016-09-05 17:57:09 +08:00
    @rockyou12 闭包是维持一个 context ,而不是 “当时”的 context ,这个问题本质是因为异步引起的,所以我说跟闭包关系不大
    wodesuck
        14
    wodesuck  
       2016-09-05 21:41:55 +08:00
    我觉得坑在 for 循环变量作用域不在循环体内(大括号里),比较反直觉。不过细想其实也很合理。
    当然直接原因就是异步和闭包,楼上都说得很好了。
    Comdex
        15
    Comdex  
       2016-09-05 23:19:25 +08:00
    自我推荐一个 go 的 worker pool lib : https://github.com/Comdex/Octopus
    cinhoo
        16
    cinhoo  
       2016-09-06 01:48:33 +08:00
    只有我一个人坚持 data race 吗。。。
    https://golang.org/doc/articles/race_detector.html 搜索 Race on loop counter 和 LZ 代码是一样的
    wweir
        17
    wweir  
       2016-09-06 08:23:17 +08:00 via Android
    把 j 作为参数传进去,而不是直接引用这个外部变量就好了
    liuscgood
        18
    liuscgood  
       2016-09-06 14:11:25 +08:00
    j 要传参过去, j 是临时变量,会跟着变.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3257 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 00:47 · PVG 08:47 · LAX 16:47 · JFK 19:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.