【发布时间】:2013-06-10 12:38:10
【问题描述】:
我有一个测试程序,当在多个 Cpu (Goroutines = Cpus) 上执行多个 goroutine 时会给出不同的结果。 “测试”是关于使用通道同步 goroutines,程序本身计算字符串中字符的出现次数。它在一个 Cpu / 一个 goroutine 上产生一致的结果。
查看 Playground 上的代码示例(注意:在本地机器上运行以在多核上执行,并观察结果数字的变化):http://play.golang.org/p/PT5jeCKgBv。
代码摘要:该程序计算 (DNA) 字符串中 4 个不同字符(A、T、G、C)的出现次数。
问题:在多个 Cpu(goroutines)上执行时,结果(出现 n 个字符)会有所不同。为什么?
说明:
- goroutine 将工作 (SpawnWork) 作为字符串生成到 Workers。设置 人工字符串输入数据(硬编码字符串被复制 n 次)。
- Goroutine Workers (Worker) 的创建等于 Cpu 的数量。
- Workers 检查字符串中的每个字符并计算 A、T 并发送 总和到一个通道,G,C 计数到另一个通道。
- SpawnWork 关闭工作字符串通道以控制 Workers(使用范围消耗字符串,当输入通道被 SpawnWork 关闭时退出)。
- 当 Workers 消耗了它的范围(字符)时,它会在退出通道上发送一个退出信号(退出
- Main (select) 循环将在收到 Cpu-count 退出次数后退出 信号。
- Main func 打印字符出现的摘要(A、T、G、C)。
简化代码:
1.“工人”(goroutines)按行计数字符:
func Worker(inCh chan *[]byte, resA chan<- *int, resB chan<- *int, quit chan bool) {
//for p_ch := range inCh {
for {
p_ch, ok := <-inCh // similar to range
if ok {
ch := *p_ch
for i := 0; i < len(ch); i++ {
if ch[i] == 'A' || ch[i] == 'T' { // Count A:s and T:s
at++
} else if ch[i] == 'G' || ch[i] == 'C' { // Count G:s and C:s
gc++
}
}
resA <- &at // Send line results on separate channels
resB <- &gc // Send line results on separate channels
} else {
quit <- true // Indicate that we're all done
break
}
}
}
2. 将工作(字符串)发送给工作人员:
func SpawnWork(inStr chan<- *[]byte, quit chan bool) {
// Artificial input data
StringData :=
"NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN\n" +
"NTGAGAAATATGCTTTCTACTTTTTTGTTTAATTTGAACTTGAAAACAAAACACACACAA\n" +
"... etc\n" +
// ...
for scanner.Scan() {
s := scanner.Bytes()
if len(s) == 0 || s[0] == '>' {
continue
} else {
i++
inStr <- &s
}
}
close(inStr) // Indicate (to Workers) that there's no more strings coming.
}
3. 主程序:
func main() {
// Count Cpus, and count down in final select clause
CpuCnt := runtime.NumCPU()
runtime.GOMAXPROCS(CpuCnt)
// Make channels
resChA := make(chan *int)
resChB := make(chan *int)
quit := make(chan bool)
inStr := make(chan *[]byte)
// Set up Workers ( n = Cpu )
for i := 0; i < CpuCnt; i++ {
go Worker(inStr, resChA, resChB, quit)
}
// Send lines to Workers
go SpawnWork(inStr, quit)
// Count the number of "A","T" & "G","C" per line
// (comes in here as ints per row, on separate channels (at and gt))
for {
select {
case tmp_at := <-resChA:
tmp_gc := <-resChB // Ch A and B go in pairs anyway
A += *tmp_at // sum of A's and T's
B += *tmp_gc // sum of G's and C's
case <-quit:
// Each goroutine sends "quit" signals when it's done. Since
// the number of goroutines equals the Cpu counter, we count
// down each time a goroutine tells us it's done (quit at 0):
CpuCnt--
if CpuCnt == 0 { // When all goroutines are done then we're done.
goto out
}
}
}
out:
// Print report to screen
}
为什么这段代码只有在单个 cpu/goroutine 上执行时才会一致?也就是说,通道似乎没有同步,或者主循环在所有 goroutine 完成之前强制退出?挠头。
(再次:在操场上查看/运行完整代码:http://play.golang.org/p/PT5jeCKgBv)
// 罗尔夫·兰帕
【问题讨论】:
-
你试过用
go run -race运行它吗? golang.org/doc/articles/race_detector.html -
我查看了代码并没有发现任何问题,所以我自己尝试了
-race。它发现了一些问题,但其中之一是Worker开头的at变量暴露给main,因为您执行tmp_at := <-resChA。也许如果你通过通道传递数字本身(而不是指向数字的指针)那么它就不会很活泼? -
@MatrixFog:我实际上已经忘记了 -race 选项。嗬!但是,删除指针并不能消除比赛,这真的让我很困惑。我会继续缩小范围。
-
请参阅下面与 Nick Craig-Wood 讨论的所有问题,无论是明显的还是(可能)隐藏的问题。一个人吸取了很多教训……好吧,去吧。 :)
-
将 goroutine 的数量与 CPU 的数量捆绑在一起是一种冒险的策略——它有时可能会导致 CPU 的利用率不足,尤其是在涉及 I/O 的情况下。在你的情况下,你可能很幸运。另一种策略是使用“过度并行”——即 goroutine 比 CPU 多。即使其中一些被阻塞(例如在通道上),CPU 内核也可以继续执行其他一些。
标签: concurrency go channel