Concurrency N>;1个goroutines(在N>;1个Cpu:s上)。为什么?
我有一个测试程序,当在多个Cpu上执行多个goroutine(Goroutines=Cpu)时,它会给出不同的结果。“测试”是关于使用通道同步goroutines,程序本身统计字符串中字符的出现次数。它在一个Cpu/一个goroutine上产生一致的结果 请参阅游乐场上的代码示例(注意:在本地计算机上运行以在多核上执行,并观察结果数字的变化): 代码摘要:程序统计(DNA)字符串中4个不同字符(A、T、G、C)的出现次数 问题:在多个Cpu(goroutines)上执行时,结果(n个字符的出现)会有所不同。为什么? 说明:Concurrency N>;1个goroutines(在N>;1个Cpu:s上)。为什么?,concurrency,go,channel,Concurrency,Go,Channel,我有一个测试程序,当在多个Cpu上执行多个goroutine(Goroutines=Cpu)时,它会给出不同的结果。“测试”是关于使用通道同步goroutines,程序本身统计字符串中字符的出现次数。它在一个Cpu/一个goroutine上产生一致的结果 请参阅游乐场上的代码示例(注意:在本地计算机上运行以在多核上执行,并观察结果数字的变化): 代码摘要:程序统计(DNA)字符串中4个不同字符(A、T、G、C)的出现次数 问题:在多个Cpu(goroutines)上执行时,结果(n个字符的出现)
inStr这里有一个工作版本,无论使用多少个CPU,都能始终产生相同的结果 这就是我所做的
- 删除传递
-在通道中传递非常快速*int
- 删除
-因为片还是引用类型,所以毫无意义*[]字节的传递
- 在将该片放入通道之前复制该片-该片指向同一内存,从而导致竞争
- 修复
处的
和
工作人员中的
的初始化-它们位于错误的位置-这是导致结果差异的主要原因gc
- 用于同步和通道关闭()
package main
import (
"bufio"
"fmt"
"runtime"
"strings"
"sync"
)
func Worker(inCh chan []byte, resA chan<- int, resB chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Worker started...")
for ch := range inCh {
at := 0
gc := 0
for i := 0; i < len(ch); i++ {
if ch[i] == 'A' || ch[i] == 'T' {
at++
} else if ch[i] == 'G' || ch[i] == 'C' {
gc++
}
}
resA <- at
resB <- gc
}
}
func SpawnWork(inStr chan<- []byte) {
fmt.Println("Spawning work:")
// An artificial input source.
StringData :=
"NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN\n" +
"NTGAGAAATATGCTTTCTACTTTTTTGTTTAATTTGAACTTGAAAACAAAACACACACAA\n" +
"CTTCCCAATTGGATTAGACTATTAACATTTCAGAAAGGATGTAAGAAAGGACTAGAGAGA\n" +
"TATACTTAATGTTTTTAGTTTTTTAAACTTTACAAACTTAATACTGTCATTCTGTTGTTC\n" +
"AGTTAACATCCCTGAATCCTAAATTTCTTCAGATTCTAAAACAAAAAGTTCCAGATGATT\n" +
"TTATATTACACTATTTACTTAATGGTACTTAAATCCTCATTNNNNNNNNCAGTACGGTTG\n" +
"TTAAATANNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN\n" +
"NNNNNNNCTTCAGAAATAAGTATACTGCAATCTGATTCCGGGAAATATTTAGGTTCATAA\n"
// Expand data n times
tmp := StringData
for n := 0; n < 1000; n++ {
StringData = StringData + tmp
}
scanner := bufio.NewScanner(strings.NewReader(StringData))
scanner.Split(bufio.ScanLines)
var i int
for scanner.Scan() {
s := scanner.Bytes()
if len(s) == 0 || s[0] == '>' {
continue
} else {
i++
s_copy := append([]byte(nil), s...)
inStr <- s_copy
}
}
close(inStr)
}
func main() {
CpuCnt := runtime.NumCPU() // Count down in select clause
CpuOut := CpuCnt // Save for print report
runtime.GOMAXPROCS(CpuCnt)
fmt.Printf("Processors: %d\n", CpuCnt)
resChA := make(chan int)
resChB := make(chan int)
inStr := make(chan []byte)
fmt.Println("Spawning workers:")
var wg sync.WaitGroup
for i := 0; i < CpuCnt; i++ {
wg.Add(1)
go Worker(inStr, resChA, resChB, &wg)
}
fmt.Println("Spawning work:")
go func() {
SpawnWork(inStr)
wg.Wait()
close(resChA)
close(resChB)
}()
A := 0
B := 0
LineCnt := 0
for tmp_at := range resChA {
tmp_gc := <-resChB // Theese go together anyway
A += tmp_at
B += tmp_gc
LineCnt++
}
if !(A+B > 0) {
fmt.Println("No A/B was found!")
} else {
ABFraction := float32(B) / float32(A+B)
fmt.Println("\n----------------------------")
fmt.Printf("Cpu's : %d\n", CpuOut)
fmt.Printf("Lines : %d\n", LineCnt)
fmt.Printf("A+B : %d\n", A+B)
fmt.Printf("A : %d\n", A)
fmt.Printf("B : %d\n", A)
fmt.Printf("AB frac: %v\n", ABFraction*100)
fmt.Println("----------------------------")
}
}
主程序包
进口(
“布菲奥”
“fmt”
“运行时”
“字符串”
“同步”
)
func Worker(inCh chan[]byte,resA chan你试过用go run-race
运行它吗?我查看了代码,没有发现任何问题,所以我自己尝试了-race
。它发现了一些问题,但其中一个问题是Worker
开始时的at
变量暴露在main
中,因为你做了tmp\u at:=@MatrixFog:我实际上已经忘记了-race选项。啊!但是,删除指针并不能消除比赛,这真的让我很困惑。我会继续尝试缩小范围。请参阅下面与Nick Craig Wood讨论的所有问题,包括明显的和潜在的问题被揭露的秘密。从中吸取了很多教训…,好吧,开始吧。)将goroutine的数量与CPU的数量捆绑在一起是一项风险策略-有时可能会导致CPU的利用率不足,尤其是在涉及I/O的情况下。在您的情况下,您可能会很幸运。另一种策略是使用“过度并行”,即使用比CPU更多的goroutine。即使某些goroutine被阻塞(例如,在通道上)CPU核心还可以执行其他功能。非常感谢您详尽的回答!不幸的是,指针本身似乎不是问题所在(删除所有PTR不会消除争用)。争用问题的确切(也是唯一)原因是与s(字符串)冲突,这是由于在读取workers时扫描程序替换了它造成的。您也已在代码中使用s(“s_copy:=append([]字节(nil),s…)”)的副本解决了此问题。非常感谢您指出了这一点!也感谢您对等待组的替代策略。Gold!:)除了指出上面的种族问题外,我还想指出为什么我的示例中的所有指针(它们实际上是有意义的)阅读DNA序列的性能测试。PTRS是一个有显著差异的优化。它可能与C、C++、D、Pascal、Phyton etc.(超级优化(GO)速度:)相比,有什么好处。比较:(GO做得很好!):提示:如果你指出种族问题是扫描字符串(你复制了),然后我可以将您的答案认可为“答案”。是的,当尝试替换“for…range”循环时,worker中变量的初始化被意外移动(它最初位于循环内部)因此,即使将指针和所有其他指针保留在原始代码中,*[]字节
和*int
指针都可能导致争用(争用检测器也同意),也解决了整个问题。但是,由于您的通道没有缓冲,所以这次没有缓冲!请使用缓冲通道尝试您的示例(给make(chan XXX,1000)
第二个参数)我猜你会看到比赛开始发挥作用。你也会看到性能提高。很高兴为你服务!有趣的是,比赛检测器没有检测到所有的比赛-这是一个很好的实验。退出的渠道策略很好-我认为等待组更整洁-也许我不应该改变这一点,因为它没有直接说明问题修正,但我忍不住要让代码更漂亮!
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.
}
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
}
package main
import (
"bufio"
"fmt"
"runtime"
"strings"
"sync"
)
func Worker(inCh chan []byte, resA chan<- int, resB chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Worker started...")
for ch := range inCh {
at := 0
gc := 0
for i := 0; i < len(ch); i++ {
if ch[i] == 'A' || ch[i] == 'T' {
at++
} else if ch[i] == 'G' || ch[i] == 'C' {
gc++
}
}
resA <- at
resB <- gc
}
}
func SpawnWork(inStr chan<- []byte) {
fmt.Println("Spawning work:")
// An artificial input source.
StringData :=
"NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN\n" +
"NTGAGAAATATGCTTTCTACTTTTTTGTTTAATTTGAACTTGAAAACAAAACACACACAA\n" +
"CTTCCCAATTGGATTAGACTATTAACATTTCAGAAAGGATGTAAGAAAGGACTAGAGAGA\n" +
"TATACTTAATGTTTTTAGTTTTTTAAACTTTACAAACTTAATACTGTCATTCTGTTGTTC\n" +
"AGTTAACATCCCTGAATCCTAAATTTCTTCAGATTCTAAAACAAAAAGTTCCAGATGATT\n" +
"TTATATTACACTATTTACTTAATGGTACTTAAATCCTCATTNNNNNNNNCAGTACGGTTG\n" +
"TTAAATANNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN\n" +
"NNNNNNNCTTCAGAAATAAGTATACTGCAATCTGATTCCGGGAAATATTTAGGTTCATAA\n"
// Expand data n times
tmp := StringData
for n := 0; n < 1000; n++ {
StringData = StringData + tmp
}
scanner := bufio.NewScanner(strings.NewReader(StringData))
scanner.Split(bufio.ScanLines)
var i int
for scanner.Scan() {
s := scanner.Bytes()
if len(s) == 0 || s[0] == '>' {
continue
} else {
i++
s_copy := append([]byte(nil), s...)
inStr <- s_copy
}
}
close(inStr)
}
func main() {
CpuCnt := runtime.NumCPU() // Count down in select clause
CpuOut := CpuCnt // Save for print report
runtime.GOMAXPROCS(CpuCnt)
fmt.Printf("Processors: %d\n", CpuCnt)
resChA := make(chan int)
resChB := make(chan int)
inStr := make(chan []byte)
fmt.Println("Spawning workers:")
var wg sync.WaitGroup
for i := 0; i < CpuCnt; i++ {
wg.Add(1)
go Worker(inStr, resChA, resChB, &wg)
}
fmt.Println("Spawning work:")
go func() {
SpawnWork(inStr)
wg.Wait()
close(resChA)
close(resChB)
}()
A := 0
B := 0
LineCnt := 0
for tmp_at := range resChA {
tmp_gc := <-resChB // Theese go together anyway
A += tmp_at
B += tmp_gc
LineCnt++
}
if !(A+B > 0) {
fmt.Println("No A/B was found!")
} else {
ABFraction := float32(B) / float32(A+B)
fmt.Println("\n----------------------------")
fmt.Printf("Cpu's : %d\n", CpuOut)
fmt.Printf("Lines : %d\n", LineCnt)
fmt.Printf("A+B : %d\n", A+B)
fmt.Printf("A : %d\n", A)
fmt.Printf("B : %d\n", A)
fmt.Printf("AB frac: %v\n", ABFraction*100)
fmt.Println("----------------------------")
}
}