goroutine 阻塞和非阻塞用法

goroutine blocking and non-blocking usage

我正在尝试了解 go-routines 的工作原理。这是一些代码:

//parallelSum.go
func sum(a []int, c chan<- int, func_id string) {
    sum := 0
    for _, n := range a {
        sum += n
    }
    log.Printf("func_id %v is DONE!", func_id)
    c <- sum
}   
func main() {
    ELEM_COUNT := 10000000
    test_arr := make([]int, ELEM_COUNT)
    for i := 0; i < ELEM_COUNT; i++ {
        test_arr[i] = i * 2 
    }
    c1 := make(chan int)
    c2 := make(chan int)
    go sum(test_arr[:len(test_arr)/2], c1, "1")
    go sum(test_arr[len(test_arr)/2:], c2, "2")
    x := <-c1
    y := <-c2

    //x, y := <-c, <-c
    log.Printf("x= %v, y = %v, sum = %v", x, y, x+y)
}   

上面的程序 运行 很好并且 returns 输出。我有同一个程序的迭代版本:

//iterSum.go
func sumIter(a []int, c *int, func_id string) {
    sum := 0
    log.Printf("entered the func %s", func_id)
    for _, n := range a { 
        sum += n
    }   
    log.Printf("func_id %v is DONE!", func_id)
    *c = sum 
}
func main() {
    */
    ELEM_COUNT := 10000000
    test_arr := make([]int, ELEM_COUNT)
    for i := 0; i < ELEM_COUNT; i++ {
        test_arr[i] = i * 2
    }
    var (
        i1 int
        i2 int
    )   
    sumIter(test_arr[:len(test_arr)/2], &i1, "1")
    sumIter(test_arr[len(test_arr)/2:], &i2, "2")
    x := i1
    y := i2

    log.Printf("x= %v, y = %v, sum = %v", x, y, x+y)
} 

我运行这个程序20次,每个程序平均运行时间。我看平均值几乎相等?并行化不应该让事情变得更快吗?我做错了什么?

这是 python 程序 运行 20 次:

iterCmd = 'go run iterSum.go'
parallelCmd = 'go run parallelSum.go'

runCount = 20


def analyzeCmd(cmd, runCount):
    runData = []
    print("running cmd (%s) for (%s) times" % (cmd, runCount))
    for i in range(runCount):
    ┆   start_time = time.time()
    ┆   cmd_out = subprocess.check_call(shlex.split(cmd))
        run_time = time.time() - start_time
    ┆   curr_data = {'iteration': i, 'run_time' : run_time}
    ┆   runData.append(curr_data)

    return runData

iterOut = analyzeCmd(iterCmd, runCount)
parallelOut = analyzeCmd(parallelCmd, runCount)

print("iter cmd data -->")
print(iterOut)

with open('iterResults.json', 'w') as f:
    json.dump(iterOut, f)

print("parallel cmd data -->")
print(parallelOut)

with open('parallelResults.json', 'w') as f:
    json.dump(parallelOut, f)

avg = lambda results: sum(i['run_time'] for i in results) / len(results)
print("average time for iterSum = %3.2f" % (avg(iterOut)))
print("average time for parallelSum = %3.2f" % (avg(parallelOut)))

这是 1 运行 的输出:

average time for iterSum = 0.27
average time for parallelSum = 0.29

所以,这里有几个问题。首先,您的通道在并发示例中没有被缓冲,这意味着接收方可能仍需要相互等待一段时间。其次,并发并不意味着并发。您确定这些实际上 运行 是并行的,而不是简单地安排在同一个 OS 线程上吗?

也就是说,您的主要问题是您的 Python 代码每次迭代都使用 go run,这意味着您记录的绝大多数 "run time" 实际上是 编译 你的代码(go run 编译然后 运行 指定的文件,它特别设计不缓存任何文件)。如果您想测试 运行 时间,请使用 Go's benchmark system,而不是您自己拼凑的版本。你会得到更准确的结果。例如,除了编译瓶颈之外,也没有办法确定 Python 代码本身引入了多少瓶颈。

哦,您应该改掉使用函数引用参数作为获取 "return" 值的方式的习惯。 Go 支持多个 returns,因此就地修改参数的 C 风格通常被认为是一种反模式,除非有真正令人信服的理由这样做。