用 go 写 worker pool 的疑惑

2016-09-05 15:37:39 +08:00
 rockyou12

研究用 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

有谁知道原因吗?

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

或者

```
j := j
go func() {
jobs <- j
}()
cinhoo
2016-09-05 15:57:49 +08:00
go run -race 检查下
suchj
2016-09-05 16:08:58 +08:00
看一下闭包的相关知识吧,主要注意变量作用域这一块,就能明白这个结果了
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
2016-09-05 16:26:55 +08:00
我自问自答了……楼上就 6 楼说到点子。这个是 go 语言匿名函数的一个天坑,参考 5.6.1
https://docs.ruanjiadeng.com/gopl-zh/ch5/ch5-06.html
0987363
2016-09-05 16:32:46 +08:00
@rockyou12 4,7 楼也是对的,哈哈
rockyou12
2016-09-05 16:34:44 +08:00
@0987363 确实……(⊙﹏⊙)b
go 这个坑真是藏得深,不知道设计的时候是怎么想的
nino
2016-09-05 17:15:41 +08:00
这跟闭包关系不大,主要原因是 go func 是异步 的
rockyou12
2016-09-05 17:46:24 +08:00
@nino 还是有点关系吧,比如在 java7 里面,匿名内部类引用外部变量是要上 final 的, java8 里面把这个都简化了可以不写,但 go 就没有检查这些,让这个地雷默默的埋在那里。
nino
2016-09-05 17:57:09 +08:00
@rockyou12 闭包是维持一个 context ,而不是 “当时”的 context ,这个问题本质是因为异步引起的,所以我说跟闭包关系不大
wodesuck
2016-09-05 21:41:55 +08:00
我觉得坑在 for 循环变量作用域不在循环体内(大括号里),比较反直觉。不过细想其实也很合理。
当然直接原因就是异步和闭包,楼上都说得很好了。
Comdex
2016-09-05 23:19:25 +08:00
自我推荐一个 go 的 worker pool lib : https://github.com/Comdex/Octopus
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
2016-09-06 08:23:17 +08:00
把 j 作为参数传进去,而不是直接引用这个外部变量就好了
liuscgood
2016-09-06 14:11:25 +08:00
j 要传参过去, j 是临时变量,会跟着变.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/304050

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX