Golang Go语言中写 worker pool 的疑惑
研究用 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
有谁知道原因吗?
Golang Go语言中写 worker pool 的疑惑
很明显 goroutine 执行的时候, j 的值变了呗。我猜可以存一下:
for j := 1; j <= 10000; j++ {
k: = j
go func() {
jobs <- k
}()
}
更多关于Golang Go语言中写 worker pool 的疑惑的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
哦 k := j 前面代码的空格位置错了 (>_<)
data race 呗
<br>go func(j int) {<br> jobs <- j<br>}(j)<br>
或者
```
j := j
go func() {
jobs <- j
}()
go run -race 检查下
看一下闭包的相关知识吧,主要注意变量作用域这一块,就能明白这个结果了
//发送 jobs
for j := 1; j <= 10000; j++ {
go func(int j) {
jobs <- j
}(j)
}
go , defer 与闭包 变量作用域~~~ 补补基础咯
我自问自答了……楼上就 6 楼说到点子。这个是 go 语言匿名函数的一个天坑,参考 5.6.1
https://docs.ruanjiadeng.com/gopl-zh/ch5/ch5-06.html
4,7 楼也是对的,哈哈
确实……(⊙﹏⊙)b
go 这个坑真是藏得深,不知道设计的时候是怎么想的
这跟闭包关系不大,主要原因是 go func 是异步 的
还是有点关系吧,比如在 java7 里面,匿名内部类引用外部变量是要上 final 的, java8 里面把这个都简化了可以不写,但 go 就没有检查这些,让这个地雷默默的埋在那里。
闭包是维持一个 context ,而不是 “当时”的 context ,这个问题本质是因为异步引起的,所以我说跟闭包关系不大
我觉得坑在 for 循环变量作用域不在循环体内(大括号里),比较反直觉。不过细想其实也很合理。
当然直接原因就是异步和闭包,楼上都说得很好了。
自我推荐一个 go 的 worker pool lib : https://github.com/Comdex/Octopus
只有我一个人坚持 data race 吗。。。
https://golang.org/doc/articles/race_detector.html 搜索 Race on loop counter 和 LZ 代码是一样的
把 j 作为参数传进去,而不是直接引用这个外部变量就好了
j 要传参过去, j 是临时变量,会跟着变.
在Go语言中实现worker pool(工作池)是一个常见的并发编程模式,它允许你管理一组worker(工作线程),这些worker可以并行地执行任务。以下是一些关于实现worker pool的关键点和建议,希望能解答你的疑惑:
-
使用goroutine和channel: Go语言中的goroutine和channel是实现并发编程的核心工具。你可以创建一个goroutine池,每个goroutine从任务队列(channel)中获取任务并执行。
-
任务队列: 使用一个无缓冲或有缓冲的channel作为任务队列。生产者将任务发送到channel,而消费者(worker)从channel中接收任务并执行。
-
worker数量: 根据你的系统资源和任务性质来确定worker的数量。可以使用一个固定数量的worker,或者根据负载动态调整worker数量。
-
优雅关闭: 确保在关闭worker pool时,能够优雅地停止所有worker,并处理完所有剩余的任务。这通常涉及到使用额外的信号channel来通知worker停止工作。
-
错误处理: 考虑任务执行过程中可能出现的错误,并设计适当的错误处理机制。例如,可以将错误发送到一个专门的错误channel中,由主goroutine进行统一处理。
-
性能调优: 根据实际负载和性能测试结果,调整worker数量、channel缓冲区大小等参数,以获得最佳性能。
希望这些建议能帮助你更好地理解和实现Go语言中的worker pool。如果有更具体的问题或需要进一步的帮助,请随时提问。