例行检查:选择是否真的选择了随机情况?

时间:2018-06-21 01:31:15

标签: go select goroutine

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

大家好,我正在按照上面的链接学习Go。

描述说:“如果准备好多个,则随机选择一个。” 但是,在使主例程等待2秒之后,才调用func fibonacci。 2秒后,频道应为以下频道: c:10次致电以从渠道中获取价值 退出:0

在我看来,两个渠道都已准备就绪。如果“如果有多个准备好,它将随机选择一个”为true,那么斐波纳契中对该案例的首次调用将有50%的机会从退出渠道获得0。但是,事实并非如此。退出之前,所有10个数字始终会被打印出来。因此,看起来选择不是随机的。我想念什么吗?

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

看起来默认代码应该打印出两个刻度。或BOOM!以500毫秒为单位。但是,只有BOOM!总是打印。如果我将默认时间从50更改为55,则同时打勾和BOOM。为什么是这样?在选择中,“ After”优先于“ 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)
        }
    }
}

3 个答案:

答案 0 :(得分:1)

在代码的第一段中,quitfor循环之前从未准备就绪。并且,在循环的每次迭代中,c都准备就绪,并且将阻塞直到发送数字为止。因此,实际上select除了写c之外什么也不能做。睡觉两秒钟根本没关系。

第二段代码未出错。 select确实确实随机选择了一个案例。但是,Go Playground具有固定的随机数生成器,这意味着在Playground上,select每次运行总是会选择一种情况。

答案 1 :(得分:1)

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

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上接收,您调用{{ 1}}会按您期望的那样执行,进入for-select,这时您会在循环fibonacci的每次迭代中不断命中该代码(它接收到,我得到递增,您再次接收到下一个值,直到由于i == 10,循环结束。然后您进入下一行并在quit通道上发送,则select执行该条件,然后程序退出。

就首先执行的语言规范而言;

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

  

1)对于语句中的所有情况,receive的通道操作数   操作以及send的通道和右侧表达式   输入后,按源顺序对语句进行一次精确评估   “选择”语句。结果是一组接收通道   从或发送到,以及要发送的相应值。任何一侧   不论哪个(如果有),都会对该评估产生影响   选择通讯操作进行。上的表达式   具有简短变量声明的RecvStmt的左侧或   作业尚未评估。 2)如果一个或多个   通信可以进行,选择一个可以进行的通信   通过统一的伪随机选择。否则,如果有   默认情况下,选择该情况。如果没有默认情况,   “选择”语句将阻塞,直到至少有一种通信可以   继续。除非所选案例是默认案例,否则各自   通信操作被执行。 3)如果所选案例是   带有简短变量声明或赋值的RecvStmt,   评估左侧表达式,并接收值(或   值)。 4)所选案例的陈述清单为   被执行。

在比赛条件下这只是伪随机,问题是您没有在创建比赛条件。

答案 2 :(得分:0)

您创建了无缓冲的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个值,然后将0发送到quit

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

c := make(chan int, 10)

但是请注意,即使执行此操作,它仍然不会按照预期的方式运行,因为在写入quit之前要读取所有10个值。您需要将作家放在同一位置,将读者放在同一位置,而不要混淆他们。