Concurrency N>;1个goroutines(在N>;1个Cpu:s上)。为什么?

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个字符的出现)

我有一个测试程序,当在多个Cpu上执行多个goroutine(Goroutines=Cpu)时,它会给出不同的结果。“测试”是关于使用通道同步goroutines,程序本身统计字符串中字符的出现次数。它在一个Cpu/一个goroutine上产生一致的结果

请参阅游乐场上的代码示例(注意:在本地计算机上运行以在多核上执行,并观察结果数字的变化):

代码摘要:程序统计(DNA)字符串中4个不同字符(A、T、G、C)的出现次数

问题:在多个Cpu(goroutines)上执行时,结果(n个字符的出现)会有所不同。为什么?

说明

  • goroutine将工作(SpawnWork)作为字符串生成到工作者。建立 人工字符串输入数据(硬编码字符串被复制n次)
  • GoroutineWorker(Worker)的创建等于Cpu的数量
  • 工作者检查字符串中的每个字符,计算A、T并发送 求和到一个通道,G,C计数到另一个通道
  • SpawnWork关闭工作字符串通道以控制工作线程(使用范围使用字符串,当输入通道被SpawnWork关闭时退出)
  • 工作者已消耗其范围(字符数)时,它会在退出通道(退出)上发送退出信号{ 持续 }否则{ 我++
    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("----------------------------")
        }
    }