gpt4 book ai didi

go - 并发和复制请求,time.After()的作用是什么?

转载 作者:行者123 更新时间:2023-12-03 10:10:08 29 4
gpt4 key购买 nike

我正在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)
}
我不明白的那一行是 case <-time.After(simulatedLoadTime)。为什么在这里?我们何时使用从该 channel 返回的值。该 channel 甚至如何在选择块之外进行通信?无论出于何种原因,该行似乎在同步结果计时方面都非常重要,因为如果我将其替换为 default:,则结果将不同步。

最佳答案

这已通过评论得到了回答(请参阅mkopriva's comment here),但让我提供“已回答”的版本。
首先,请注意一点:

done := make(chan interface{})

我通常在这里看到 make(chan struct{})。由于从未发送任何实际值,因此 channel 的类型无关紧要,但是发送空的 struct值根本不占用空间,而发送空的 interface{}则需要空间。1
现在,我们要做的是,在结局2中:
  • 等待(或至少假装等待)一些服务器应答;
  • 如果发生超时,请停止等待服务器;和
  • 将我们的ID传送到结果 channel

  • (如果 done channel 已关闭)(指示其他人击败了我们去做所有事情),而不用理会上述任何一项。
    复杂的是,即使没有得到答案,我们也会记录等待的时间。
    主要goroutine:
  • 创建done channel ,其唯一目的是成为close d,以便从该 channel 接收立即在EOF处返回其缺少零值的零值;
  • 派生了一些(具体为10个)工作程序goroutine。
  • 等待第一个传递结果(可能由于超时导致结果缺乏结果)
  • 关闭done channel 以终止其余工作进程;和
  • 打印最终结果。

  • 我们感兴趣的是为什么闭包的代码是用代码片段编写的:
        select {
    case <-done:
    case <-time.After(simulatedLoadTime):
    }
    在里面。
    这里的技巧是 select预先评估其所有替代项。因此,它在开始选择过程之前会评估 done channel ,但还会调用 time.After()。然后, select等待具有值或在 channel 末尾且因此具有EOF的任何一个,以先到者为准。
    如果尚未有goroutine将结果发送回主goroutine,则 done channel 将不会关闭。此时,所有goroutine将在 done channel 上阻塞。但是所有goroutine也会调用 time.Aftertime.After代码启动一个goroutine,该goroutine在一段时间后将在 channel 上发送当前时间。然后,它返回该 channel 。因此,这两个 <-操作中的至少一个将完成:要么 done channel 将被关闭,要么被关闭,由于EOF,我们将获得一个零值,或者 time.After返回的 channel 将有一个发送时间做到这一点,我们将获得这一值(value)。不管我们实际获得哪个值,我们都会将该值放在地板上,但是两个 <-运算符之一最终将解除阻塞这一事实保证了该goroutine最终将能够继续执行。
    首先发生的事件将是 done channel 的关闭或时间的接收。我们不知道这是哪一个,因为我们不知道 done channel 关闭将花费多长时间,但是时间的上限仍然是传递给 time.After的持续时间。也就是说, done会(最终)发生,或者在我们选择的时间之后, time.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仅从 channel 读取一个值。为了确保我们不会卡在这里,我们还有另一个 select。我们将尝试发送ID,但是如果 done channel 现在已关闭或被关闭,则停止发送。然后,我们将记录并返回。

    1我一直认为Go应当具有预先声明的空struct类型,就像方便和样式一样。我们将在此处将其用于 done channel 。我们将其用于仅作为集合而存在的 map ,除了它们还将具有预先声明的“仅方便样式”类型。但这完全是另一回事。
    2这里没有特别好的理由使用闭包。未导出的普通函数也可以正常工作。假设我们使用的是闭包,我们可以捕获 done channel , wg *WaitGroup值和 result channel ,而不用将它们作为参数。对我来说尚不清楚,为什么作者选择将其编写为可以成为函数的闭包,然后又不理会闭包为我们带来的任何好处。

    关于go - 并发和复制请求,time.After()的作用是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65446569/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com