研究用 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 1
看一下闭包的相关知识吧,主要注意变量作用域这一块,就能明白这个结果了
|
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 是临时变量,会跟着变.
|