问题描述
我绝对是Go中并发的新手。我试图通过两个古丁鱼来产生竞争条件,并编写了以下代码:
var x int = 2
func main() {
go f1(&x)
go f2(&x)
time.Sleep(time.Second)
fmt.Println("Final value of x:",x)
}
func f1(px *int) {
for i := 0; i < 10; i++ {
*px = *px * 2
fmt.Println("f1:",*px)
}
}
func f2(px *int) {
for i := 0; i < 10; i++ {
*px = *px + 1
fmt.Println("f2:",*px)
}
}
在输出的每个变体中,控制台中都有f2的所有输出行,仅在此之后才有f1的输出。这是一个示例:
f2: 3
f2: 4
f2: 5
f2: 6
f2: 7
f2: 8
f2: 9
f2: 10
f2: 21
f2: 22
f1: 20
f1: 44
f1: 88
f1: 176
f1: 352
f1: 704
f1: 1408
f1: 2816
f1: 5632
f1: 11264
Final value of x: 11264
但是您可以看到确实有一些f1的执行是在f2的执行之间进行的:
f2: 10
f2: 21
所以我有两个问题:
- 为什么f1的所有Printl()在执行f2的Println()之后严格执行(我认为它们必须以某种方式混合)
- 为什么更改代码中goroutine的顺序
go f2(&x)
go f1(&x)
代替
go f1(&x)
go f2(&x)
输出线的顺序反之亦然,f1为第一,f2'2为第二。我的意思是代码中的鱼肉素顺序如何影响它们的执行?
解决方法
首先,您看到的行为是由于tight-loop引起的。 Go调度程序无法合理地知道如何分担工作量,因为您的循环很短且不占用大量时间(例如,低于10ms阈值)
Go调度程序的工作方式是一个非常板式的话题,并且在Go版本之间有所变化,但引用this artcile:
如果循环不包含任何抢占点(例如函数调用或 分配内存),它们将阻止其他goroutine运行
通常在10毫秒后才会发生抢占。
在现实世界中,处理循环通常会调用一些阻塞调用(DB操作,REST / gRPC调用等)-这将提示Go调度程序将其他goroutine设置为“ Runnable”。您可以通过在代码中插入时间来模拟这一点。在循环中休眠:https://play.golang.org/p/_C3QOUMNOaU
还有其他方法可以放弃(runtime.Gosched),但通常应避免使用这些技术。避免死循环,让时间表按部就班。
执行顺序
当涉及多个goroutines时(如@Marc所述),在goroutines之间没有协调时,执行顺序是不确定的。
Go可以使用许多工具来协调例行活动:
阻止当前goroutine并允许安排其他goroutine。使用这些技术可以保证较大任务的精确排序。
但是,无法预测在这些协调检查点之间运行的各个指令的执行顺序。