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

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

用于取消機上例程的 golang 設計模式

用于取消機上例程的 golang 設計模式

Go
米琪卡哇伊 2023-06-19 15:31:12
我是一個 golang 新手,正試圖了解這個問題的正確設計模式。我當前的解決方案似乎非常冗長,而且我不確定更好的方法是什么。我正在嘗試設計一個系統:執行 N 個協程一旦可用,就返回每個 goroutine 的結果如果一個 goroutine 返回一個特定的值,它應該殺死其他 goroutines 將取消。目標:我想啟動多個 goroutine,但如果一個例程返回特定結果,我想取消這些例程。我試圖了解我的代碼是否超級“臭”,或者這是否是規定的做事方式。我仍然沒有很好的感覺,所以任何幫助將不勝感激。這是我寫的:package mainimport (    "context"    "fmt"    "time")func main() {    ctx := context.Background()    ctx, cancel := context.WithCancel(ctx)    fooCheck := make(chan bool)    barCheck := make(chan bool)    go foo(ctx, 3000, fooCheck)    go bar(ctx, 5000, barCheck)    for fooCheck != nil ||        barCheck != nil {        select {        case res, ok := <-fooCheck:            if !ok {                fooCheck = nil                continue            }            if res == false {                cancel()            }            fmt.Printf("result of foocheck: %t\n", res)        case res, ok := <-barCheck:            if !ok {                barCheck = nil                continue            }            fmt.Printf("result of barcheck: %t\n", res)        }    }    fmt.Printf("here we are at the end of the loop, ready to do some more processing...")}func foo(ctx context.Context, pretendWorkTime int, in chan<- bool) {    fmt.Printf("simulate doing foo work and pass ctx down to cancel down the calltree\n")    time.Sleep(time.Millisecond * time.Duration(pretendWorkTime))    select {    case <-ctx.Done():        fmt.Printf("\n\nWe cancelled this operation!\n\n")        break    default:        fmt.Printf("we have done some foo work!\n")        in <- false    }    close(in)}func bar(ctx context.Context, pretendWorkTime int, in chan<- bool) {    fmt.Printf("simulate doing bar work and pass ctx down to cancel down the calltree\n")    time.Sleep(time.Millisecond * time.Duration(pretendWorkTime))    select {    case <-ctx.Done():        fmt.Printf("\n\nWe cancelled the bar operation!\n\n")        break    default:        fmt.Printf("we have done some bar work!\n")        in <- true    }    close(in)}輸出按預期工作,但恐怕我正在做一些決定,稍后會吹走我的腳。
查看完整描述

2 回答

?
嚕嚕噠

TA貢獻1784條經驗 獲得超7個贊

我會使用單一渠道來傳達結果,因此收集結果要容易得多,而且它會根據其性質自動“縮放”。如果您需要識別結果的來源,只需使用包含來源的包裝器即可。是這樣的:


type Result struct {

? ? ID? ? ?string

? ? Result bool

}

為了模擬“真實”工作,工作人員應該使用一個循環以迭代方式完成他們的工作,并且在每次迭代中他們應該檢查取消信號。是這樣的:


func foo(ctx context.Context, pretendWorkMs int, resch chan<- Result) {

? ? log.Printf("foo started...")

? ? for i := 0; i < pretendWorkMs; i++ {

? ? ? ? time.Sleep(time.Millisecond)

? ? ? ? select {

? ? ? ? case <-ctx.Done():

? ? ? ? ? ? log.Printf("foo terminated.")

? ? ? ? ? ? return

? ? ? ? default:

? ? ? ? }

? ? }

? ? log.Printf("foo finished")

? ? resch <- Result{ID: "foo", Result: false}

}

在我們的示例中, thebar()是相同的,只是將所有foo單詞替換為bar。


現在執行作業并提前終止其余作業(如果確實符合我們的預期),如下所示:


ctx, cancel := context.WithCancel(context.Background())

defer cancel()


resch := make(chan Result, 2)


log.Println("Kicking off workers...")

go foo(ctx, 3000, resch)

go bar(ctx, 5000, resch)


for i := 0; i < cap(resch); i++ {

? ? result := <-resch

? ? log.Printf("Result of %s: %v", result.ID, result.Result)

? ? if !result.Result {

? ? ? ? cancel()

? ? ? ? break

? ? }

}

log.Println("Done.")

運行此應用程序將輸出(在Go Playground上嘗試):


2009/11/10 23:00:00 Kicking off workers...

2009/11/10 23:00:00 bar started...

2009/11/10 23:00:00 foo started...

2009/11/10 23:00:03 foo finished

2009/11/10 23:00:03 Result of foo: false

2009/11/10 23:00:03 Done.

有些事情要注意。如果我們由于意外結果提前終止,cancel()函數將被調用,我們從循環中跳出??赡芷溆嗟墓ぷ魅藛T也同時完成他們的工作并發送他們的結果,這不會成為問題,因為我們使用了緩沖通道,所以他們的發送不會阻塞并且他們會正確結束。此外,如果他們沒有同時完成,他們會檢查ctx.Done()他們的循環,并且他們提前終止,所以 goroutines 被很好地清理了。


另請注意,上面代碼的輸出不打印bar terminated。這是因為main()函數在循環后立即終止,一旦main()函數結束,它不會等待其他非maingoroutines 完成。如果應用程序不會立即終止,我們也會看到該行被打印出來。time.Sleep()如果我們在末尾添加一個main()


log.Println("Done.")

time.Sleep(3 * time.Millisecond)

輸出將是(在Go Playground上嘗試):


2009/11/10 23:00:00 Kicking off workers...

2009/11/10 23:00:00 bar started...

2009/11/10 23:00:00 foo started...

2009/11/10 23:00:03 foo finished

2009/11/10 23:00:03 Result of foo: false

2009/11/10 23:00:03 Done.

2009/11/10 23:00:03 bar terminated.

現在,如果您必須等待所有工作人員“正?!被颉疤崆啊苯Y束才能繼續,您可以通過多種方式實現。


一種方法是使用sync.WaitGroup.?另一種方法是讓每個工作人員發送一個Result無論他們如何結束,并且Result可以包含終止條件,例如normalor?aborted。goroutinemain()可以繼續接收循環,直到它n從 接收到值resch。如果選擇此解決方案,您必須確保每個工作人員發送一個值(即使發生恐慌)以main()在這種情況下(例如使用 using?defer)不阻塞。



查看完整回答
反對 回復 2023-06-19
?
米脂

TA貢獻1836條經驗 獲得超3個贊

我將針對您所說的內容分享最簡單的模式。您可以將其擴展到更復雜的場景。


func doStuff() {

    // This can be a chan of anything.

    msgCh := make(chan string)


    // This is how you tell your go-routine(s) to stop, by closing this chan.

    quitCh := make(chan struct{})

    defer close(quitCh)


    // Start all go routines.

    for whileStart() {

        go func() {

            // Do w/e you need inside of your go-routine.


            // Write back the result.

            select {

            case msgCh <- "my message":

                // If we got here then the chan is open.

            case <-quitCh:

                // If we got here then the quit chan was closed.

            }

        }()

    }


    // Wait for all go routines.

    for whileWait() {

        // Block until a msg comes back.

        msg := <-msgCh

        // If you found what you want.

        if msg == stopMe {

            // It's safe to return because of the defer earlier.

            return

        }

    }

}


查看完整回答
反對 回復 2023-06-19
  • 2 回答
  • 0 關注
  • 144 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

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

幫助反饋 APP下載

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

公眾號

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