并发和复制的请求,什么是时间。After() for?

我正在阅读 Go 中的并发性,并且非常接近尾声!总的来说,这是一本很棒的读物。在其中一个示例中,作者描述了如何模拟请求复制。代码示例如下:


func main() {

    doWork := func(

        done <-chan interface{},

        id int,

        wg *sync.WaitGroup,

        result chan<- int,

    ) {

        started := time.Now()

        defer wg.Done()


        // Simulate random load

        simulatedLoadTime := time.Duration(1*rand.Intn(5)) * time.Second


        /** use two separate select blocks because we want to send/receive two different values, the time.After (receive) and the id (send).

        / if they were in the same select block, then we could only use one value at a time, the other will get lost. */

        select {

        // do not want to return on <-done because we still want to log the time it took

        case <-done:

        case <-time.After(simulatedLoadTime):

        }


        select {

        case <-done:

        case result <- id:

        }


        took := time.Since(started)

        // Display how long handlers would have taken

        if took < simulatedLoadTime {

            took = simulatedLoadTime

        }

        fmt.Printf("%v took %v\n", id, took)

    }


    done := make(chan interface{})

    result := make(chan int)


    var wg sync.WaitGroup

    wg.Add(10)


    for i := 0; i < 10; i++ {

        go doWork(done, i, &wg, result)

    }


    firstReturned := <-result

    close(done)

    wg.Wait()


    fmt.Printf("Received an answer from #%v\n", firstReturned)

}

我不明白的一句话是.为什么会在这里?我们什么时候会利用从该通道返回的值。该通道是如何在选择块之外进行通信的?无论出于何种原因,这条线似乎在同步结果的时间方面都是不可或缺的,因为如果我用a替换它,结果就不同步了。case <-time.After(simulatedLoadTime)default:


侃侃尔雅
浏览 121回答 1
1回答

子衿沉夜

这已经通过评论得到了回答(请参阅mkopriva的评论),但让我提供一个“答案化”版本。首先,一个小小的旁白:done&nbsp;:=&nbsp;make(chan&nbsp;interface{})我通常看到这里。由于从未发送过任何实际值,因此通道的类型并不重要,但是发送空值根本不占用空间,而发送空值则占用空间。1 个make(chan struct{})structinterface{}现在,我们在这里要做的,在结束语中,2是:等待(或至少假装等待)某个服务器应答;如果发生超时,请停止等待服务器;和将我们的ID发送到结果渠道或者如果频道关闭(表明其他人打败我们做任何事情),不要打扰上述任何一项。done作为一个复杂因素,我们还将记录我们等待了多长时间,即使我们没有得到答案。主要功能:创建通道,其唯一目的是 d,以便从它接收立即返回它们在 EOF 处缺少值的零值;doneclose剥离这些工人的一些数字(具体为10);等待第一个交付结果(可能是由于超时,结果而导致的结果缺失)关闭通道以使其余工作线程终止;和done打印最终结果。我们感兴趣的是为什么闭包的代码是用代码片段编写的:&nbsp;&nbsp;select&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;case&nbsp;<-done:&nbsp;&nbsp;&nbsp;&nbsp;case&nbsp;<-time.After(simulatedLoadTime): &nbsp;&nbsp;&nbsp;&nbsp;}在其中。这里的诀窍是预先评估其所有替代方案。因此,在开始选择过程之前,它会评估通道,但也调用 。然后,等待具有值或位于通道末端并因此具有EOF的任何一个,以先发生者为准。selectdonetime.After()select如果还没有戈鲁廷将结果发送回主戈鲁廷,则该通道将不会关闭。此时,所有&nbsp;goroutines 都将阻塞在通道上。但是所有的戈鲁丁人也会叫.donedonetime.After该代码启动一个 goroutine,经过一段时间后,它将在通道上发送当前时间。然后,它返回该通道。因此,这两个操作中至少有一个将完成:通道将关闭或 get 关闭,并且由于 EOF,我们将在其上获得零值,或者返回的通道将有一个时间发送到它,我们将收到该值。无论我们实际获得哪个值,我们都会将值放在地板上,但是两个运算符中的一个最终会解除阻塞的事实保证了这个goroutine最终能够继续。time.After<-donetime.After<-首先发生的事件将是通道的关闭或时间的接收。我们不知道这是哪一个,因为我们不知道通道关闭需要多长时间,但是时间的上限是我们传递给的持续时间有多长。也就是说,要么发生(最终),要么在我们选择的时间之后,部分发生。其中之一肯定会发生。donedonetime.Afterdonetime.After现在,如果我们不关心记录我们花费的时间,我们可以把它写成:&nbsp;&nbsp;&nbsp;&nbsp;select&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;case&nbsp;<-done:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return &nbsp;&nbsp;&nbsp;&nbsp;case&nbsp;<-time.After(simulatedLoadTime):&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;everything&nbsp;else&nbsp;happens&nbsp;here &nbsp;&nbsp;&nbsp;&nbsp;}但请注意原始代码中的注释://&nbsp;do&nbsp;not&nbsp;want&nbsp;to&nbsp;return&nbsp;on&nbsp;<-done&nbsp;because&nbsp;we&nbsp;still&nbsp;want&nbsp;to&nbsp;log&nbsp;...因此,这解释了缺乏.return超时后,我们现在必须尝试将我们的ID发送到主goroutine。但是,我们可能无法做到这一点:其他一些工作 goroutine 可能会击败我们发送,而主 goroutine 仅从通道读取一个值。为了确保我们不会被困在这里,我们还有另一个.我们将尝试发送我们的 ID,但如果频道现在关闭或关闭,则停止。然后,我们将记录并返回。selectdone1 个我一直认为Go应该有一个预先声明的空结构类型,只是为了方便和风格的东西。我们在这里将其用于我们的频道。我们将它用于仅作为集合而存在的映射,除了它们还将具有预先声明的仅方便和样式类型。但这完全是另一回事。done阿拉伯数字这里没有特别好的理由使用闭包。未导出的普通函数也可以正常工作。假设我们使用的是闭包,我们可以捕获通道、值和通道,而不是将它们作为参数。我不清楚为什么作者选择把它写成一个可能成为函数的闭包,然后不去打扰闭包给我们带来的任何好东西。donewg *WaitGroupresult
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go