GoLang - 顺序与并发

标签 go concurrency

我有两个版本的阶乘。并发与顺序。

这两个程序都会计算 10 个“1000000”次的阶乘。

阶乘并发处理

package main

import (
    "fmt"
    //"math/rand"
    "sync"
    "time"
    //"runtime"
)

func main() {
    start := time.Now()
    printFact(fact(gen(1000000)))
    fmt.Println("Current Time:", time.Now(), "Start Time:", start, "Elapsed Time:", time.Since(start))
    panic("Error Stack!")
}

func gen(n int) <-chan int {
    c := make(chan int)
    go func() {
        for i := 0; i < n; i++ {
            //c <- rand.Intn(10) + 1
            c <- 10
        }
        close(c)
    }()
    return c
}

func fact(in <-chan int) <-chan int {
    out := make(chan int)
    var wg sync.WaitGroup
    for n := range in {
        wg.Add(1)
        go func(n int) {
            //temp := 1
            //for i := n; i > 0; i-- {
            //  temp *= i
            //}
            temp := calcFact(n)
            out <- temp
            wg.Done()
        }(n)
    }
    go func() {
        wg.Wait()
        close(out)
    }()
    return out
}

func printFact(in <-chan int) {
    //for n := range in {
    //  fmt.Println("The random Factorial is:", n)
    //}
    var i int
    for range in {
        i ++
    }
    fmt.Println("Count:" , i)
}

func calcFact(c int) int {
    if c == 0 {
        return 1
    } else {
        return calcFact(c-1) * c
    }
}

//###End of Factorial Concurrent 

阶乘顺序处理

package main

import (
    "fmt"
    //"math/rand"
    "time"
    "runtime"
)

func main() {
    start := time.Now()
    //for _, n := range factorial(gen(10000)...) {
    //  fmt.Println("The random Factorial is:", n)
    //}
    var i int
    for range factorial(gen(1000000)...) {
        i++
    }
    fmt.Println("Count:" , i)
    fmt.Println("Current Time:", time.Now(), "Start Time:", start, "Elapsed Time:", time.Since(start))
}

func gen(n int) []int {
    var out []int
    for i := 0; i < n; i++ {
        //out = append(out, rand.Intn(10)+1)
        out = append(out, 10)
    }
    println(len(out))
    return out
}

func factorial(val ...int) []int {
    var out []int
    for _, n := range val {
        fa := calcFact(n)
        out = append(out, fa)
    }
    return out
}

func calcFact(c int) int {
    if c == 0 {
        return 1
    } else {
        return calcFact(c-1) * c
    }
}

//###End of Factorial sequential processing

我的假设是并发处理会比顺序处理快,但在我的 Windows 机器上顺序处理比并发执行得更快。

我使用的是 8 核/i7/32 GB 内存。

我不确定是程序有问题还是我的基本理解是正确的。

附注- 我是 GoLang 的新手。

最佳答案

与顺序版本相比,程序的并发版本总是很慢。然而,原因与您要解决的问题的性质和行为有关。

您的程序是并发的,但不是并行的。每个 callFact 都在它自己的 goroutine 中运行,但没有划分需要完成的工作量。每个 goroutine 必须执行相同的计算并输出相同的值。

这就像有一个任务需要将一些文本复制一百次。您只有一个 CPU(暂时忽略内核)。

当你启动一个顺序进程时,你将 CPU 指向原文一次,并要求它写下 100 次。 CPU 必须管理单个任务。

对于 goroutines,CPU 被告知有一百个任务必须同时完成。恰好它们都是相同的任务。但是 CPU 不够聪明,无法知道这一点。

所以它做了和上面一样的事情。尽管现在每个任务都小了 100 倍,但仍然只有一个 CPU。所以 CPU 必须做的工作量仍然是相同的,除了同时管理 100 个不同事物的所有额外开销。因此,它失去了一部分效率。

要查看性能的改进,您需要适当的并行性。一个简单的示例是将阶乘输入数字大致在中间拆分并计算 2 个较小的阶乘。然后将它们组合在一起:

// not an ideal solution

func main() {
    ch := make(chan int)
    r := 10
    result := 1
    go fact(r, ch)
    for i := range ch {
        result *= i
    }
    fmt.Println(result)
}

func fact(n int, ch chan int) {
    p := n/2
    q := p + 1
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        ch <- factPQ(1, p)
        wg.Done()
    }()
    go func() {
        ch <- factPQ(q, n)
        wg.Done()
    }()
    go func() {
        wg.Wait()
        close(ch)
    }()
}

func factPQ(p, q int) int {
    r := 1
    for i := p; i <= q; i++ {
        r *= i
    }
    return r
}

工作代码:https://play.golang.org/p/xLHAaoly8H

现在您有两个 goroutine 朝着同一个目标努力,而不仅仅是重复相同的计算。

关于 CPU 内核的注意事项:

在您的原始代码中,顺序版本的操作肯定是由运行时环境和操作系统分布在各个 CPU 内核之间。所以它在一定程度上仍然具有并行性,你只是无法控制它。

在并发版本中也发生了同样的情况,但同样如上所述,goroutine 上下文切换的开销导致性能下降。

关于GoLang - 顺序与并发,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39299711/

相关文章:

ubuntu - 服务器上的简单 go 二进制部署不起作用

java - CompleatableFuture在handle方法中可以异常完成吗?

java - 在 Java 中分析并发程序行为的特性

c# - 使用 C++/CLI 的任务并行库 Task.ContinueWith

java - 在 run 方法中使用同步块(synchronized block)的效果很奇怪

go - 我应该在哪里存储服务注册服务器的端点?

戈朗 : Convert float to hex string

java - 在 Scala 和第三方 Java 库中使用 Akka 的最佳实践

go - 如何在 go/goa 框架中正确弃用 API 端点?

multithreading - 戈朗 : why using goroutines to parallelize calls ends up being slower?