研究用 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
有谁知道原因吗?
1 fds 2016-09-05 15:49:32 +08:00 很明显 goroutine 执行的时候, j 的值变了呗。我猜可以存一下: for j := 1; j <= 10000; j++ { k: = j go func() { jobs <- k }() } |
2 fds 2016-09-05 15:52:41 +08:00 哦 k := j 前面代码的空格位置错了 ~~~~(>_<)~~~~ |
3 cinhoo 2016-09-05 15:52:53 +08:00 data race 呗 |
![]() | 4 shidenggui 2016-09-05 15:53:13 +08:00 ``` go func(j int) { jobs <- j }(j) ``` 或者 ``` j := j go func() { jobs <- j }() |
5 cinhoo 2016-09-05 15:57:49 +08:00 go run -race 检查下 |
![]() | 6 suchj 2016-09-05 16:08:58 +08:00 ![]() 看一下闭包的相关知识吧,主要注意变量作用域这一块,就能明白这个结果了 |
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 与闭包 变量作用域~~~ 补补基础咯 |
![]() | 8 rockyou12 OP 我自问自答了……楼上就 6 楼说到点子。这个是 go 语言匿名函数的一个天坑,参考 5.6.1 https://docs.ruanjiadeng.com/gopl-zh/ch5/ch5-06.html |
![]() | 11 nino 2016-09-05 17:15:41 +08:00 这跟闭包关系不大,主要原因是 go func 是异步 的 |
![]() | 12 rockyou12 OP @nino 还是有点关系吧,比如在 java7 里面,匿名内部类引用外部变量是要上 final 的, java8 里面把这个都简化了可以不写,但 go 就没有检查这些,让这个地雷默默的埋在那里。 |
![]() | 13 nino 2016-09-05 17:57:09 +08:00 @rockyou12 闭包是维持一个 context ,而不是 “当时”的 context ,这个问题本质是因为异步引起的,所以我说跟闭包关系不大 |
![]() | 14 wodesuck 2016-09-05 21:41:55 +08:00 我觉得坑在 for 循环变量作用域不在循环体内(大括号里),比较反直觉。不过细想其实也很合理。 当然直接原因就是异步和闭包,楼上都说得很好了。 |
![]() | 15 Comdex 2016-09-05 23:19:25 +08:00 自我推荐一个 go 的 worker pool lib : https://github.com/Comdex/Octopus |
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 代码是一样的 |
![]() | 17 wweir 2016-09-06 08:23:17 +08:00 via Android 把 j 作为参数传进去,而不是直接引用这个外部变量就好了 |
![]() | 18 liuscgood 2016-09-06 14:11:25 +08:00 j 要传参过去, j 是临时变量,会跟着变. |