N> 1个goroutines的不同结果(在N> 1个Cpu:s上)。为什么?

时间:2013-06-13 23:12:06

标签: concurrency go channel

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

请参阅playground上的代码示例(注意:在本地计算机上运行以在多核上执行,并观察生成的数字会有所不同):http://play.golang.org/p/PT5jeCKgBv

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

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

描述

  1. goroutine产生( SpawnWork )作为工人的字符串。设置 人工字符串输入数据(硬编码字符串被复制n次)。
  2. Goroutine 工人(工人)的创建等于Cpu的数量。
  3. 工作人员检查字符串中的每个字符并计算A,T并发送 加入一个通道,G,C计入另一个通道。
  4. SpawnWork 关闭工作字符串通道以控制Workers(使用范围消耗字符串,当输入通道被 SpawnWork 关闭时退出)。
  5. 工人消耗其范围(字符数)时,它会在退出通道上发送退出信号(退出< - true)。这些“脉冲”将出现Cpu次数(Cpu count = goroutines count)。
  6. (select)循环将在收到Cpu-count退出次数后退出 信号。
  7. Main func打印出Chars(A,T,G,C)的出现总结。
  8. 简化代码

    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
    }
    

    为什么只有在singel cpu / goroutine上执行时,此代码才会一致?也就是说,通道似乎没有同步,或主循环在所有goroutine完成之前强行退出?抓头。

    (再次:在操场上查看/运行完整代码:http://play.golang.org/p/PT5jeCKgBv

    // Rolf Lampa

1 个答案:

答案 0 :(得分:2)

这是一个工作版本,无论使用多少cpus,它都能始终产生相同的结果。

这就是我做的事情

  • 删除*int的传递 - 非常活泼以传入频道!
  • 删除*[]byte的传递 - 无论如何切片都是参考类型
  • 在将切片放入通道之前复制切片 - 切片指向导致竞赛的相同内存
  • 修复atgcWorker的初始化 - 它们位于错误的位置 - 这是导致结果差异的主要原因
  • 使用sync.WaitGroup进行同步,并使用频道关闭()

我使用-race parameter of go build来查找和修复数据竞赛。

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("----------------------------")
    }
}