为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
侃侃尔雅 2022-08-01 10:30:43
我正在阅读 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:
查看完整描述

1 回答

?
子衿沉夜

TA贡献1828条经验 获得超3个赞

这已经通过评论得到了回答(请参阅mkopriva的评论),但让我提供一个“答案化”版本。

首先,一个小小的旁白:

done := make(chan interface{})

我通常看到这里。由于从未发送过任何实际值,因此通道的类型并不重要,但是发送空值根本不占用空间,而发送空值则占用空间。1 个make(chan struct{})structinterface{}

现在,我们在这里要做的,在结束语中,2是:

  • 等待(或至少假装等待)某个服务器应答;

  • 如果发生超时,请停止等待服务器;和

  • 将我们的ID发送到结果渠道

或者如果频道关闭(表明其他人打败我们做任何事情),不要打扰上述任何一项。done

作为一个复杂因素,我们还将记录我们等待了多长时间,即使我们没有得到答案。

主要功能:

  • 创建通道,其唯一目的是 d,以便从它接收立即返回它们在 EOF 处缺少值的零值;doneclose

  • 剥离这些工人的一些数字(具体为10);

  • 等待第一个交付结果(可能是由于超时,结果而导致的结果缺失)

  • 关闭通道以使其余工作线程终止;和done

  • 打印最终结果。

我们感兴趣的是为什么闭包的代码是用代码片段编写的:

  select {    case <-done:    case <-time.After(simulatedLoadTime):
    }

在其中。

这里的诀窍是预先评估其所有替代方案。因此,在开始选择过程之前,它会评估通道,但也调用 。然后,等待具有值或位于通道末端并因此具有EOF的任何一个,以先发生者为准。selectdonetime.After()select

如果还没有戈鲁廷将结果发送回主戈鲁廷,则该通道将不会关闭。此时,所有 goroutines 都将阻塞在通道上。但是所有的戈鲁丁人也会叫.donedonetime.After

该代码启动一个 goroutine,经过一段时间后,它将在通道上发送当前时间。然后,它返回该通道。因此,这两个操作中至少有一个将完成:通道将关闭或 get 关闭,并且由于 EOF,我们将在其上获得零值,或者返回的通道将有一个时间发送到它,我们将收到该值。无论我们实际获得哪个值,我们都会将值放在地板上,但是两个运算符中的一个最终会解除阻塞的事实保证了这个goroutine最终能够继续。time.After<-donetime.After<-

首先发生的事件将是通道的关闭或时间的接收。我们不知道这是哪一个,因为我们不知道通道关闭需要多长时间,但是时间的上限是我们传递给的持续时间有多长。也就是说,要么发生(最终),要么在我们选择的时间之后,部分发生。其中之一肯定会发生。donedonetime.Afterdonetime.After

现在,如果我们不关心记录我们花费的时间,我们可以把它写成:

    select {    case <-done:        return
    case <-time.After(simulatedLoadTime):        // everything else happens here
    }

但请注意原始代码中的注释:

// do not want to return on <-done because we still want to log ...

因此,这解释了缺乏.return

超时后,我们现在必须尝试将我们的ID发送到主goroutine。但是,我们可能无法做到这一点:其他一些工作 goroutine 可能会击败我们发送,而主 goroutine 仅从通道读取一个值。为了确保我们不会被困在这里,我们还有另一个.我们将尝试发送我们的 ID,但如果频道现在关闭或关闭,则停止。然后,我们将记录并返回。selectdone


1 个我一直认为Go应该有一个预先声明的空结构类型,只是为了方便和风格的东西。我们在这里将其用于我们的频道。我们将它用于仅作为集合而存在的映射,除了它们还将具有预先声明的仅方便和样式类型。但这完全是另一回事。done

阿拉伯数字这里没有特别好的理由使用闭包。未导出的普通函数也可以正常工作。假设我们使用的是闭包,我们可以捕获通道、值和通道,而不是将它们作为参数。我不清楚为什么作者选择把它写成一个可能成为函数的闭包,然后不去打扰闭包给我们带来的任何好东西。donewg *WaitGroupresult


查看完整回答
反对 回复 2022-08-01
  • 1 回答
  • 0 关注
  • 83 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信