go routine:select 真的会随机选择一个案例吗?

go routine: does select really pick a random case?

上下文:https://tour.golang.org/concurrency/5

大家好,我正在按照上面的link学习围棋。

描述说 "It chooses one at random if multiple are ready." 然而,在调用 func fibonacci 之前让主例程等待 2 秒。 2 秒后通道应如下所示: c:10次调用以从渠道获取价值 退出:0

在我看来两个频道都准备好了。如果 "It chooses one at random if multiple are ready" 为真,那么有 50% 的机会在 fibonacci 案例上的第一次调用将从退出通道中获得 0。然而,事实并非如此。所有 10 个数字总是会在退出前打印出来。因此它看起来不像 selection 是随机的。我错过了什么吗?

package main

import "fmt"
import "time"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    time.Sleep(2 * time.Second)
    fibonacci(c, quit)
}

此外,下一页: https://tour.golang.org/concurrency/6

看起来默认代码应该打印出任一勾号。或砰!在 500 毫秒。但是,只有 BOOM!始终打印。如果我将默认时间从 50 更改为 55,则会打印 tick 和 BOOM。为什么是这样? After 是否优先于 select 中的 Tick?

package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(55 * time.Millisecond)
        }
    }
}

您创建了一个无缓冲的 chan:

c := make(chan int)

这意味着从 chan 读取的任何内容都将被阻塞,直到向其写入内容。任何写入 chan 的内容都会阻塞,直到有内容从中读取。

因此在这段代码中:

    for i := 0; i < 10; i++ {
        fmt.Println(<-c)
    }
    quit <- 0

<-c 块,直到有东西被放在 c 上。所以它会一直等到 time.Sleep() 完成后,然后到达:

    case c <- x:

然后它交替阻塞来回读一个值,写一个值,直到读到10个值,然后它发送0quit

要创建缓冲通道,您需要指定缓冲区的大小。

c := make(chan int, 10)

但是请注意,即使您这样做,它仍然不会像您预期的那样运行,因为您在写入 quit 之前读取了所有 10 个值。你要把作者放在一个地方,把读者放在一个地方,不要混在一起。

在代码的第一个片段中,quitfor 循环之前从未就绪。并且,在循环的每次迭代中,c 都准备就绪,并且会阻塞直到发送一个数字。所以 select 除了写信给 c 真的什么也做不了。睡两秒根本无所谓

第二段代码没有错误。 select 确实随机选择了一个案例。然而,Go Playground 有一个固定的随机生成器,这意味着在 Playground 上,select 总是会在每个 运行.

中选择一个特定的情况。

我认为您对 main 方法中发生的事情有误...为了清楚起见,我将分解我认为正在发生的事情

func main() {
c := make(chan int)
quit := make(chan int) // make a couple channels
go func() {
    for i := 0; i < 10; i++ {
        fmt.Println(<-c) // blocked here in goroutine
    }
    quit <- 0
}() // go func { ... }() - you're both writing this closure and invoking it as a goroutine
time.Sleep(2 * time.Second) // sleep for 2 seconds
fibonacci(c, quit) // call fibonacci passing in the channels
}

所以这里实际发生的是你将这个闭包称为 goroutine 然后等待 2 秒,在此期间你的 goroutine 仍然坐在 for 循环的主体中等待在 c 上接收,你调用fibonacci 按照您预期的方式执行,进入 for-select,此时您在循环的每次迭代中不断点击该代码 c <- x(它接收,i 递增,您接收再次,下一个值直到循环由于 i == 10 而结束)。然后你继续下一行并在退出通道上发送,select 执行该条件并且你的程序退出。

就语言规范所说的首先执行的内容而言;

"select" 语句的执行分几个步骤进行:

1) For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated. 2) If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed. Unless the selected case is the default case, the respective communication operation is executed. 3) If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are evaluated and the received value (or values) are assigned. 4) The statement list of the selected case is executed.

这只是竞争条件下的伪随机,问题是您没有创建竞争条件。