如何在 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? 描述了一些其他功能。
请注意,术语 process 和 thread 已经相当模糊,尽管对这些区别有一些普遍的共识。见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提供的。但是你肯定看到了很多东西!
为了演示,我制作了一个程序,试图说明如何使用 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? 描述了一些其他功能。
请注意,术语 process 和 thread 已经相当模糊,尽管对这些区别有一些普遍的共识。见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提供的。但是你肯定看到了很多东西!