Golang协程调度,为什么这两个代码片段的结果一个有序一个无序?

问题一:为什么这两个代码片段的结果一个有序一个无序?

问题二:我查网上资料写着Go 协程调度不是随机的,但它也不是按顺序的。它是一种抢占式、工作窃取的调度模型,我没理解这和随机的有啥区别

代码片段一:输出的结果有序

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan struct{})
    for i := 0; i < 10; i++ {
        go func(num int) {
            for {
                <-ch
                fmt.Println(num)
            }
        }(i)
        time.Sleep(time.Millisecond)
    }
    time.Sleep(time.Second)
    for j := 0; j < 10; j++ {
        ch <- struct{}{}
        time.Sleep(time.Millisecond)
    }
    time.Sleep(time.Minute)
}

代码片段二:输出的结果无序

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan struct{})
    for i := 0; i < 10; i++ {
        go func(num int) {
            for {
                <-ch
                fmt.Println(num)
            }
        }(i)
        time.Sleep(time.Millisecond)
    }
    time.Sleep(time.Second)
    for j := 0; j < 10; j++ {
        ch <- struct{}{}
    }
    time.Sleep(time.Minute)
}

go
最佳答案

因为第一个你睡的时间太长了。 可以这么理解,买煎饼的的人,每买一个就刷一个视频,刷完再买第二个。 因为做煎饼的在你刷视频期间就做完了,所以完全和你买煎饼的顺序一样。

第二个乱序,是因为,你连续买煎饼,买了一个,又下单一个,连续下了10单,再看视频,10个做煎饼的师傅,做的速度追不上你下单的速度,但是每个做煎饼的师傅做的速度可能有细小差别,就导致,做完煎饼的时间有先有后。

5个月前 评论
讨论数量: 4

因为第一个你睡的时间太长了。 可以这么理解,买煎饼的的人,每买一个就刷一个视频,刷完再买第二个。 因为做煎饼的在你刷视频期间就做完了,所以完全和你买煎饼的顺序一样。

第二个乱序,是因为,你连续买煎饼,买了一个,又下单一个,连续下了10单,再看视频,10个做煎饼的师傅,做的速度追不上你下单的速度,但是每个做煎饼的师傅做的速度可能有细小差别,就导致,做完煎饼的时间有先有后。

5个月前 评论

第二个因为,先起了10个goroutine,然后极短的时间一次性把所有要消费的数据全部纳入到了channel中。最后10个已经启动的goroutine,可能会存在10个goroutine同时竞争接收通道中的值,因此输出可能是无序的。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan struct{})
    for i := 0; i < 10; i++ {
        go func(num int) {
           fmt.Println("num...", num)
           for {
              <-ch
     fmt.Println(num)
           }
        }(i)
        time.Sleep(time.Millisecond)
    }
    time.Sleep(time.Second)
    for j := 0; j < 10; j++ {
        fmt.Println("j", j)
        ch <- struct{}{}
        //time.Sleep(time.Millisecond)
    }
    time.Sleep(time.Minute)
}

/*
num... 0
num... 1
num... 2
num... 3
num... 4
num... 5
num... 6
num... 7
num... 8
num... 9
j 0
j 1
j 2
j 3
j 4
j 5
j 6
j 7
j 8
j 9
9
3
2
5
1
6
0
7
8
4
*/

第一个则虽然goroutine也已经启动了10个,但是生产侧则是等一段时间往channel里塞一个数据,所以每次只有一个goroutine有机会从通道接收值,因此输出通常是有序的。

5个月前 评论

这个要从channel源码解释。我的理解:不论是第一个程序还是第二个程序,第一个for循环执行完后,因为通道没有值,所有go程都挂起了,都被放到了阻塞队列里,channel的阻塞队列时fifo的。第二个for循环,往通道里写内容后,go程开始被唤醒。但是问题的关键时被唤醒的go程不是马上还是执行后面的打印语句,go程的状态由阻塞状态变成runnable状态,至于什么时候执行,要看调度都的调度。对于第一个程序,因为由sleep,前一个go程从被唤醒后,有足够的时候被调度执行,所有是顺序打印的,但是第二个程序10个go程几乎是同时唤醒,所有他们都是runnable状态,至于谁先执行,就看运气了。可能是这个原因,但是我了解好像对于gmp模型来说,每个p都有一个自己的队列,这个队列里的go程怎么个循序执行就不清楚了。

5个月前 评论

我的理解是在goRoutine的local队列排了10个P等待消费,第一个代码片是间隔1毫秒,消费一次,就是有序的消费。第二个代码片是一次性去消费10个消息,就变成了抢占,就看谁先打印了。

4个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!