为什么两个goroutine的控制台输出看起来像同步

问题描述

我绝对是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

所以我有两个问题:

  1. 为什么f1的所有Printl()在执行f2的Println()之后严格执行(我认为它们必须以某种方式混合)
  2. 为什么更改代码中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。使用这些技术可以保证较大任务的精确排序。

但是,无法预测在这些协调检查点之间运行的各个指令的执行顺序。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...