如何在 Go/Golang 中正确演示并发性和并行性?

How can I properly demonstrate concurrency AND parallelism in Go/Golang?

为了演示,我制作了一个程序,试图说明如何使用 Go 制作并发和 运行s 并行的程序。输出似乎表明它至少是 运行ning 并发,但我不确定如何判断它是否是 运行ning 并行。我已经阅读了很多关于如何使用 goroutines 并在 WaitGroup 中将它们同步在一起的资源,但是对于这些 运行 是否在单独的线程上似乎存在很多困惑。作为刚接触 Go 的新手程序员,我将不胜感激一些澄清!

package main

import (
    "fmt"
    "sync"
    "time"
)

//take order
func takeOrder1(wg *sync.WaitGroup) {
    s_sleep(1000)
    fmt.Println("\nTaking order...", t_time())
    go takeOrder2(wg)
}
func takeOrder2(wg *sync.WaitGroup) {
    s_sleep(1500)
    fmt.Println("\nOrder tooken!", t_time())
    wg.Done()
}

//make fires
func makeFries1(wg *sync.WaitGroup) {
    s_sleep(1500)
    fmt.Println("\nFrying fries...", t_time())
    go makeFries2(wg)
}
func makeFries2(wg *sync.WaitGroup) {
    s_sleep(3000)
    fmt.Println("\nFries Fried!", t_time())
    wg.Done()
}

//burn burger
func makeBurger1(wg *sync.WaitGroup) {
    s_sleep(2000)
    fmt.Println("\nFlipping burger...", t_time())
    go makeBurger2(wg)
}
func makeBurger2(wg *sync.WaitGroup) {
    s_sleep(5000)
    fmt.Println("\nCooked a burger!", t_time())
    wg.Done()
}

//cook drink
func pourDrink1(wg *sync.WaitGroup) {
    s_sleep(1000)
    fmt.Println("\nPutting ice in cup...", t_time())
    go pourDrink2(wg)
}
func pourDrink2(wg *sync.WaitGroup) {
    s_sleep(3000)
    fmt.Println("\nPouring soda in cup...", t_time())
    go pourDrink3(wg)
}
func pourDrink3(wg *sync.WaitGroup) {
    s_sleep(2500)
    fmt.Println("\nDrink poured!", t_time())
    wg.Done()
}

//wipe table
func cleanTable1(wg *sync.WaitGroup) {
    s_sleep(1000)
    fmt.Println("\n'Cleaning' table....", t_time())
    go cleanTable2(wg)
}
func cleanTable2(wg *sync.WaitGroup) {
    s_sleep(1500)
    fmt.Println("\nTable 'clean'!", t_time())
    wg.Done()
}

//delay
func s_sleep(x int) { time.Sleep(time.Duration(x) * time.Millisecond) }

//just to print time
func t_time() string {
    return time.Now().Format("15:04:05")
}

//create array of tasks to complete
var McDolansTasks = []func(*sync.WaitGroup){
    takeOrder1, makeFries1, makeBurger1, pourDrink1, cleanTable1}

//main function
func main() {
    var waitGroup sync.WaitGroup
    // Set number of effective goroutines we want to wait upon
    waitGroup.Add(len(McDolansTasks))

    for _, task := range McDolansTasks {
        // Pass reference to WaitGroup instance
        // Each of the tasks should call on WaitGroup.Done()
        go task(&waitGroup)
    }

    // Wait until all goroutines have completed execution.
    waitGroup.Wait()
    println("\nClock out for the day!")
}

(https://play.golang.org/p/4OhaMn1zMT9)

简短的回答是你不能显示并发和并行,因为也许它们不是(并发和并行) .例如,如果你只有一个执行者——一个“P”,在一个内部 Go 运行时间模型中——你一次只会 运行ning 一个 goroutine。

... there seems to be a lot of confusion on whether these run on separate threads ...

Go 一开始就没有根据“线程”来定义,所以这个问题无法回答。这不是很令人满意,但除非您添加诸如“今天在我的系统上”之类的限定词,否则 就是 您得到的全部!所以也许您想今天在我的系统上添加限定符

规范允许 goroutines到运行并行,并定义了Go的并发模型,但不要求任何特定的实施。这让 Go 实现者可以自由使用任何看起来合适的东西。我在上面提到了“内部 Go 运行时间模型”,但没有说 哪个 。我的意思是 今天在你的系统上

目前 Go 有一个主要实现,尽管该实现有许多版本,根据不同 CPU 和 Go 版本的需要。那个实现 has a runtime(你可以在 runtime 包中调用一些东西在某种程度上控制它),那个 运行time 确实有一个内部模型。 那个内部模型中,系统——OSand/or Go实现下面的其他实现——有线程。这些系统线程在内部称为“M”:假定 OS 以一个线程 运行time 开始,m0,之后 运行time 将创建新的附加线程OS 在合适的时候发帖。

Povilas Versockas has a (now slightly out of date) write-up on scheduling in the Go runtime here. The answer to When will Go scheduler create a new M and P? 描述了一些其他功能。

请注意,术语 processthread 已经相当模糊,尽管对这些区别有一些普遍的共识。见What is the difference between a process and a thread?一个goroutine非常一个线程,但一般来说,大多数现代线程系统分配一个“线程ID”给每个线程。 Goroutines 具体 没有 有 ID,这意味着没有办法与任何其他 goroutine 进行有意义的交谈或谈论特定的 goroutine。这有助于迫使 Go 程序员使用通道进行通信。 (它不能完全强制这样做,但确实有帮助。)

要真正演示并行操作,您可以超越 Go 规范,进入未定义行为的领域。每当 Go 规范 未定义行为时,它可能由其他东西定义,然后其他东西可能允许你展示并行性。不幸的是,因为你已经超越了 Go 规范,你现在不再首先编写(便携式)Go 程序:你的演示只是演示了一些关于 具体实现.

您也可以使用调试器,例如delve。调试器通常必须以各种定义不明确的方式与 运行time 和 OS 进行交互,才能 能够向您展示程序中正在发生的事情 ,通过这样做,他们往往会表现出各种未按规范定义但实际上像这样工作的行为。你不会得到任何关于这些的保证,除非它们是由(例如)你的OS提供的。但是你肯定看到了很多东西!