执行goroutine时的并发与并行

时间:2018-10-24 17:56:06

标签: go concurrency

一个相当天真的问题。我正在阅读Go-concurrency教程,并且遇到了这个https://tour.golang.org/concurrency/4

我修改了代码以在fibonacci函数中添加打印语句。所以代码看起来像

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
        fmt.Println("here")
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

我将其作为输出

here
here
here
here
here
here
here
here
here
here
0
1
1
2
3
5
8
13
21
34

我期望herenumbers被交错。 (由于例程被并发执行) 我想我缺少有关例程的一些基本知识。不太清楚。

3 个答案:

答案 0 :(得分:2)

这里有几件事。

  1. 您有2个goroutines,一个正在运行main(),另一个正在运行fibonacci()。因为这是一个小程序,所以go调度程序没有充分的理由不在同一线程上一个接一个地运行它们,因此这是始终发生的,尽管不能保证。由于goroutine中的main()正在等待chan,因此首先安排了fibonacci()例程。重要的是要记住,goroutines不是线程,它们是go调度程序根据自己的喜好在线程上运行的例程。

  2. 因为您要将缓冲通道的长度传递给fibonacci(),所以几乎可以肯定(从不依赖此行为)是cap(c) {{1 }}打印后,将填充heres,完成channel循环,将for发送到close,然后完成chan。然后安排goroutine goroutine并打印main()斐波那契。如果缓冲的cap(c)已满,那么chan将被重新安排: https://play.golang.org/p/_IgFIO1K-Dc

  3. 通过睡觉,您可以告诉go计划程序放弃控制。但是实际上永远不要这样做。以某种方式进行重组,或者如果需要,可以使用Waitgroup。参见:https://play.golang.org/p/Ln06-NYhQDj

  4. 我认为您正在尝试这样做:https://play.golang.org/p/8Xo7iCJ8Gj6

答案 1 :(得分:1)

我认为您正在观察的是Go拥有自己的调度程序,同时“并发”和“并行性”之间存在区别。用罗伯·派克(Rob Pike)的话说:Concurrency is not Parallelism

Goroutine比OS线程轻得多,它们在“用户区”(在Go进程内)而不是操作系统中进行管理。有些程序正在运行成千上万(甚至数万个)goroutine,而分配的操作系统线程肯定会少得多。 (这是Go在具有许多例程的异步程序中的主要优势之一)

由于您的程序非常简单,并且通道已缓冲,因此在写入通道时不会阻塞:

c <- x

斐波那契goroutine在完成短循环之前不会被抢占。

即使fmt.Println("here")并没有确定性地引入抢占性-我在编写此答案时从中学到了一些东西。它被缓存,like the analagous printf and scanf from C。 (请参见源代码https://github.com/golang/go/blob/master/src/fmt/print.go

有兴趣的是,如果要人为地控制OS线程数,可以在命令行上设置GOMAXPROCS环境变量:

~$ GOMAXPROCS=1 go run main.go

但是,对于您的简单程序而言,可能没有明显的区别 ,因为Go运行时仍然能够完全针对1个OS线程调度许多goroutine。 >

例如,这是您程序的一个较小变体。通过使通道缓冲区变小(5),但仍然迭代10次,我们引入了一个点,在该点可以抢占fibonacci go例程 (但不一定),在该点上它至少可以阻塞一次写入频道:

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
        fmt.Println("here")
    }
    close(c)
}

func main() {
    c := make(chan int, 5)
    go fibonacci(cap(c)*2, c)
    for i := range c {
        fmt.Println(i)
    }
}

~$ GOMAXPROCS=1 go run main.go
here
here
here
here
here
here
0
1
1
2
3
5
8
here
here
here
here
13
21
34

Long explanation here,简短的解释是,go例程可能会暂时阻塞很多原因,这是go调度程序安排另一个go例程执行的理想机会。

答案 2 :(得分:0)

如果您在斐波那契循环中的 fmt.Println 之后添加它,您将看到结果以您期望的方式交错:

    time.Sleep(1 * time.Second)

这让 Go 调度器有理由阻止 fibonacci() 协程的执行足够长的时间,以允许 main() 协程从通道读取。