亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

并發和復制請求,time.After() 是干什么用的?

并發和復制請求,time.After() 是干什么用的?

Go
汪汪一只貓 2022-07-25 10:37:29
我正在閱讀 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). 為什么會在這里?我們什么時候使用過從該通道返回的值。該通道甚至是如何在選擇塊之外進行通信的?無論出于何種原因,這條線似乎在同步結果的時間方面非常重要,因為如果我用 a 替換它,default:結果就會不同步。
查看完整描述

1 回答

?
浮云間

TA貢獻1829條經驗 獲得超4個贊

這已由評論回答(請參閱此處的 mkopriva 評論),但讓我提供一個“答案化”的版本。

首先,一個小問題:

done := make(chan interface{})

我經常看到make(chan struct{})這里。由于從未發送任何實際值,因此通道的類型并不重要,但發送空struct值根本不占用空間,而發送空值則interface{}占用空間。1

現在,我們要在這里做的,在一個閉包中,2是:

  • 等待(或至少假裝等待)某個服務器回答;

  • 如果發生超時,停止等待服務器;和

  • 將我們的 ID 傳遞到結果通道

或者如果done頻道關閉(表明其他人在做任何事情上都比我們強),請不要理會上述任何事情。

作為一個復雜因素,我們還將記錄我們等待了多長時間,即使我們沒有得到答案。

主協程:

  • 創建done通道,其唯一目的是closed 以便從它接收立即在 EOF 處返回其缺少值的零值;

  • 衍生出一些(具體來說是 10 個)這些工作 goroutine;

  • 等待第一個傳遞結果(可能是由于超時導致的缺少結果,結果)

  • 關閉done通道以使剩余的工作人員終止;和

  • 打印最終結果。

我們感興趣的是為什么閉包的代碼是用代碼片段編寫的:

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

在里面。

這里的訣竅是預先select評估其所有備選方案。因此,它會在開始選擇過程之前評估done通道,但也會調用。time.After()然后select等待任何一個具有值或處于通道末端并因此具有 EOF,以先發生者為準。

如果還沒有goroutine 將結果發送回主 goroutine,done則不會關閉通道。 此時,所有goroutine 都會阻塞done通道。但是所有的 goroutine 也會調用time.After.

time.After代碼啟動了一個 goroutine,在一段時間后,它將在通道上發送當前時間。然后它返回該頻道。因此,這兩個操作中至少有一個<-將完成:done通道將關閉或關閉,并且由于 EOF,我們將在其上獲得零值,或者返回的通道time.After將有一個時間發送給它,我們將收到該值。無論我們實際得到哪個值,我們都會將值放在地板上,但是兩個<-運算符中的一個最終解除阻塞的事實保證了這個 goroutine 最終能夠繼續進行。

首先發生的事件將是done通道的關閉,或時間的接收。我們不知道這是哪一個,因為我們不知道done通道關閉需要多長時間,但是時間的上限是我們傳遞給的持續時間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 只從通道中讀取一個值。為了確保我們不會被困在這里,我們還有另一個select. 我們將嘗試發送我們的 ID,但如果done通道現在或即將關閉,則停止。然后我們將記錄并返回。


1我一直認為 Go 應該有一個預先聲明的空結構類型,作為一種方便和風格的東西。我們將在此處將其用于我們的done頻道。我們會將其用于僅作為集合存在的地圖,除了它們也將具有預先聲明的僅方便和樣式的類型。但這完全是另一回事。

2這里沒有特別好的理由使用閉包。未導出的普通函數也可以正常工作。鑒于我們正在使用閉包,我們可以捕獲done通道、wg *WaitGroup值和result通道,而不是將它們作為參數。我不清楚為什么作者選擇把它寫成一個可能是一個函數的閉包,然后不理會閉包給我們帶來的任何好處。


查看完整回答
反對 回復 2022-07-25
  • 1 回答
  • 0 關注
  • 103 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號