欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言協(xié)程通道使用的問題小結(jié)

 更新時(shí)間:2024年08月29日 10:29:44   作者:2301_76723322  
本文主要介紹了Go語言協(xié)程通道使用的問題小結(jié),詳細(xì)的介紹了使用的一些重要問題,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

關(guān)于Go語言中通道(channel)使用的一些重要問題:

1. 為什么用完通道要關(guān)閉?

  • 資源管理:關(guān)閉通道可以釋放與之相關(guān)的資源,包括內(nèi)存和goroutine。
  • 通知接收方:關(guān)閉通道是一種向接收方發(fā)出"不再有數(shù)據(jù)"信號(hào)的方式。
  • 避免死鎖:如果接收方在等待已經(jīng)不再使用的通道,可能會(huì)導(dǎo)致程序死鎖。

2. 不關(guān)閉通道的風(fēng)險(xiǎn):

  • 資源泄露:未關(guān)閉的通道可能導(dǎo)致相關(guān)的goroutine無法退出,造成資源泄露。
  • 潛在的死鎖:如果有g(shù)oroutine在等待接收數(shù)據(jù),而發(fā)送方已經(jīng)不再發(fā)送,可能導(dǎo)致程序死鎖。
  • 難以調(diào)試:未關(guān)閉的通道可能導(dǎo)致程序行為不可預(yù)測,增加調(diào)試難度。

3.怎么優(yōu)雅地關(guān)閉?

  • 優(yōu)雅地關(guān)閉通道的方法:

    a. 使用defer關(guān)鍵字:

    ch := make(chan int)
    defer close(ch)
    // 使用通道...
    

    b. 使用sync.Once確保只關(guān)閉一次:

    var once sync.Once
    close := func() { once.Do(func() { close(ch) }) }
    // 在適當(dāng)?shù)臅r(shí)候調(diào)用close()
    

    c. 使用專門的停止通道:

    done := make(chan struct{})
    go func() {
        // 執(zhí)行操作...
        close(done)
    }()
    // 等待操作完成
    <-done
    

    d. 使用context包來管理取消操作:

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    // 使用ctx來控制goroutine的生命周期
    

記住,只有發(fā)送方應(yīng)該關(guān)閉通道,接收方不應(yīng)該關(guān)閉通道。同時(shí),確保不要多次關(guān)閉同一個(gè)通道,這會(huì)導(dǎo)致panic。

4.有緩存和無緩存的通道有什么區(qū)別?

有緩存和無緩存的通道(channel)在 Go 語言中有一些關(guān)鍵的區(qū)別,兩者的詳細(xì)比較如下:

  • 定義方式:

    • 無緩存通道:ch := make(chan int)
    • 有緩存通道:ch := make(chan int, capacity) (capacity > 0)
  • 容量:

    • 無緩存通道:容量為 0
    • 有緩存通道:容量大于 0,由創(chuàng)建時(shí)指定
  • 阻塞行為:

    • 無緩存通道:
      • 發(fā)送操作會(huì)阻塞,直到有接收者準(zhǔn)備好接收數(shù)據(jù)
      • 接收操作會(huì)阻塞,直到有發(fā)送者發(fā)送數(shù)據(jù)
    • 有緩存通道:
      • 只有當(dāng)緩沖區(qū)滿時(shí),發(fā)送操作才會(huì)阻塞
      • 只有當(dāng)緩沖區(qū)空時(shí),接收操作才會(huì)阻塞
  • 同步特性:

    • 無緩存通道:提供了強(qiáng)同步保證,發(fā)送和接收操作同時(shí)發(fā)生
    • 有緩存通道:提供了一定程度的異步性,發(fā)送和接收可以在不同時(shí)間發(fā)生
  • 使用場景:

    • 無緩存通道:適用于需要即時(shí)響應(yīng)或嚴(yán)格同步的場景
    • 有緩存通道:適用于需要一定程度解耦或緩沖的場景
  • 性能考慮:

    • 無緩存通道:可能導(dǎo)致更多的上下文切換
    • 有緩存通道:可以減少goroutine的阻塞,潛在地提高性能
  • 關(guān)閉行為:

    • 兩種通道關(guān)閉后的行為相同:
      • 可以繼續(xù)從關(guān)閉的通道接收數(shù)據(jù),直到通道為空
      • 向關(guān)閉的通道發(fā)送數(shù)據(jù)會(huì)導(dǎo)致 panic
  • 容量檢查:

    • 無緩存通道:len(ch) 始終為 0,cap(ch) 始終為 0
    • 有緩存通道:len(ch) 返回當(dāng)前在緩沖區(qū)中的元素?cái)?shù)量,cap(ch) 返回緩沖區(qū)的容量
  • 內(nèi)存使用:

    • 無緩存通道:內(nèi)存占用較小
    • 有緩存通道:根據(jù)容量的大小,可能占用更多內(nèi)存
  • 死鎖風(fēng)險(xiǎn):

    • 無緩存通道:如果沒有相應(yīng)的接收操作,單獨(dú)的發(fā)送操作更容易導(dǎo)致死鎖
    • 有緩存通道:提供了一定的緩沖,減少了即時(shí)死鎖的風(fēng)險(xiǎn),但仍需注意緩沖區(qū)滿時(shí)的情況
  • 用于范圍循環(huán):

    • 兩種通道都可以用于 range 循環(huán),但有緩存通道可能更適合批處理場景

選擇使用哪種類型的通道取決于具體的應(yīng)用場景、同步需求、性能考慮和代碼的復(fù)雜性。無緩存通道提供了更強(qiáng)的同步保證,而有緩存通道則提供了更大的靈活性和潛在的性能優(yōu)勢。

5.代碼詳解:

var once sync.Once
close := func() { once.Do(func() { close(ch) }) }
  • var once sync.Once:

    • 這里聲明了一個(gè) sync.Once 類型的變量 once。
    • sync.Once 是 Go 標(biāo)準(zhǔn)庫中的一個(gè)同步原語,用于確保某個(gè)函數(shù)只被執(zhí)行一次。
  • close := func() { ... }:

    • 這定義了一個(gè)匿名函數(shù),并將其賦值給變量 close
    • 這個(gè)函數(shù)可以在之后被多次調(diào)用。
  • once.Do(func() { close(ch) }):

    • once.Do() 方法接受一個(gè)函數(shù)作為參數(shù)。
    • 這個(gè)函數(shù)只會(huì)在第一次調(diào)用 once.Do() 時(shí)執(zhí)行,后續(xù)的調(diào)用將不會(huì)執(zhí)行這個(gè)函數(shù)。
    • 在這里,傳遞給 once.Do() 的函數(shù)是 func() { close(ch) },這個(gè)函數(shù)會(huì)關(guān)閉通道 ch。
  • 整體功能:

    • 這段代碼創(chuàng)建了一個(gè)安全的關(guān)閉通道的機(jī)制。
    • 即使 close 函數(shù)被多次調(diào)用,ch 通道也只會(huì)被關(guān)閉一次。
    • 這避免了因多次關(guān)閉同一個(gè)通道而導(dǎo)致的 panic。
  • 使用場景:

    • 在并發(fā)環(huán)境中,當(dāng)多個(gè) goroutine 可能會(huì)嘗試關(guān)閉同一個(gè)通道時(shí),這種方法特別有用。
    • 它確保了通道只會(huì)被關(guān)閉一次,無論 close 函數(shù)被調(diào)用多少次。
  • 優(yōu)點(diǎn):

    • 線程安全:sync.Once 保證了在并發(fā)環(huán)境中的安全性。
    • 避免 panic:防止了多次關(guān)閉通道導(dǎo)致的 panic。
    • 簡潔:提供了一種簡潔的方式來處理"只執(zhí)行一次"的邏輯。
  • 注意事項(xiàng):

    • 雖然這個(gè)方法可以防止多次關(guān)閉通道,但仍然需要確保不會(huì)向已關(guān)閉的通道發(fā)送數(shù)據(jù)。
    • 這個(gè)模式主要用于在不確定通道是否已關(guān)閉的情況下安全地關(guān)閉通道。

這種模式在處理通道關(guān)閉時(shí)非常有用,特別是在復(fù)雜的并發(fā)場景中,可以有效地防止由于重復(fù)關(guān)閉通道而導(dǎo)致的錯(cuò)誤。

done := make(chan struct{})
go func() {
    // 執(zhí)行操作...
    close(done)
}()
// 等待操作完成
<-done
  • done := make(chan struct{})

    • 創(chuàng)建一個(gè)無緩沖的通道 done。
    • 使用 struct{} 類型是因?yàn)槲覀冎魂P(guān)心通道的關(guān)閉狀態(tài),不需要傳遞實(shí)際的數(shù)據(jù)。
    • struct{} 是一個(gè)空結(jié)構(gòu)體,不占用任何內(nèi)存,是信號(hào)通道的理想選擇。
  • go func() { ... }()

    • 啟動(dòng)一個(gè)新的 goroutine 來執(zhí)行匿名函數(shù)。
    • 這允許主 goroutine 繼續(xù)執(zhí)行,而不會(huì)被阻塞。
  • // 執(zhí)行操作...

    • 這里代表在新啟動(dòng)的 goroutine 中執(zhí)行的實(shí)際操作。
    • 可能是一些耗時(shí)的任務(wù),如 I/O 操作、計(jì)算等。
  • close(done)

    • 當(dāng)操作完成時(shí),關(guān)閉 done 通道。
    • 關(guān)閉通道會(huì)向所有正在等待該通道的 goroutine 發(fā)送一個(gè)信號(hào)。
  • <-done

    • 主 goroutine 在這里等待 done 通道的關(guān)閉。
    • 這行代碼會(huì)阻塞,直到 done 通道被關(guān)閉。
    • 一旦 done 通道關(guān)閉,這個(gè)接收操作就會(huì)立即完成,允許程序繼續(xù)執(zhí)行。

這種模式的主要優(yōu)點(diǎn)和使用場景:

  • 同步:提供了一種簡單的方式來同步主 goroutine 和后臺(tái) goroutine,確保主 goroutine 等待某個(gè) goroutine 完成實(shí)際工作后再繼續(xù)執(zhí)行。

  • 非阻塞操作:允許后臺(tái)任務(wù)非阻塞地執(zhí)行,同時(shí)主 goroutine 可以等待它完成。

  • 超時(shí)處理:可以很容易地添加超時(shí)邏輯,例如:

    select {
    case <-done:
        // 操作完成
    case <-time.After(5 * time.Second):
        // 超時(shí)處理
    }
    
  • 取消操作:可以擴(kuò)展這個(gè)模式來支持取消操作,例如使用 context 包。

  • 無數(shù)據(jù)傳遞:使用 struct{} 類型的通道強(qiáng)調(diào)了這是一個(gè)純粹的信號(hào)機(jī)制,不涉及數(shù)據(jù)傳輸。

  • 資源管理:一旦操作完成并且通道關(guān)閉,相關(guān)的 goroutine 就可以退出,防止資源泄露。

這種模式在 Go 的并發(fā)編程中非常常見,特別是當(dāng)你需要等待一個(gè)或多個(gè)后臺(tái)任務(wù)完成時(shí)。它提供了一種簡潔、高效的方式來協(xié)調(diào)不同的 goroutine,

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 使用ctx來控制goroutine的生命周期
  • context.Background()

    • 這是一個(gè)空的 Context,通常用作根 Context。
    • 它永遠(yuǎn)不會(huì)被取消,沒有值,也沒有截止時(shí)間。
  • context.WithCancel(context.Background())

    • 基于 Background Context 創(chuàng)建一個(gè)新的可取消的 Context。
    • 返回新創(chuàng)建的 Context 和一個(gè)取消函數(shù)。
  • ctx, cancel := ...

    • ctx 是新創(chuàng)建的可取消 Context。
    • cancel 是用于取消這個(gè) Context 的函數(shù)。
  • defer cancel()

    • 確保在函數(shù)退出時(shí)調(diào)用 cancel 函數(shù)。
    • 這是一個(gè)好習(xí)慣,可以防止資源泄露。

使用 ctx 來控制 goroutine 的生命周期的詳細(xì)解讀:

  • 啟動(dòng) goroutine:

    go func() {
        for {
            select {
            case <-ctx.Done():
                // Context被取消,退出goroutine
                return
            default:
                // 執(zhí)行實(shí)際工作
                // ...
            }
        }
    }()
    
  • 傳遞 Context:

    • 將 ctx 傳遞給需要控制的函數(shù)或方法。
    go doSomething(ctx)
    
  • 在函數(shù)中使用 Context:

    func doSomething(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                // 處理取消邏輯
                return
            case <-time.After(1 * time.Second):
                // 執(zhí)行定期任務(wù)
            }
        }
    }
    
  • 取消操作:

    • 當(dāng)需要停止所有使用這個(gè) Context 的操作時(shí),調(diào)用 cancel() 函數(shù)。
  • 處理取消:

    • 在 goroutine 中,可以通過檢查 ctx.Done() 通道來判斷是否應(yīng)該退出。
    • ctx.Err() 可以提供額外的錯(cuò)誤信息(如超時(shí)或取消)。
  • 超時(shí)控制:

    • 可以使用 context.WithTimeout 或 context.WithDeadline 來添加超時(shí)控制。
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
  • 值傳遞:

    • Context 還可以用來傳遞請求范圍的值。
    ctx = context.WithValue(ctx, key, value)
    

優(yōu)點(diǎn):

  • 優(yōu)雅地控制 goroutine 的生命周期。
  • 避免 goroutine 泄露。
  • 在整個(gè)調(diào)用鏈中傳播取消信號(hào)。
  • 可以輕松實(shí)現(xiàn)超時(shí)控制。
  • 提供了一種標(biāo)準(zhǔn)的方式來傳遞請求范圍的值。

注意事項(xiàng):

  • 不要將 Context 存儲(chǔ)在結(jié)構(gòu)體中,而應(yīng)該將其作為第一個(gè)參數(shù)傳遞給需要它的函數(shù)。
  • Context 應(yīng)該及時(shí)取消,以釋放資源。
  • 不要傳遞 nil Context,如果不確定使用什么 Context,可以使用 context.TODO()

6.可能疑惑的問題:

done := make(chan struct{})
go func() {
   // 執(zhí)行操作...
   close(done)
}()
// 等待操作完成
<-done

在該代碼中,啟動(dòng)的協(xié)程下面緊跟著的代碼就是<-done,如果done通道中無數(shù)據(jù),那么主goroutine不應(yīng)該會(huì)阻塞在這里嗎,為什么在回答的第2點(diǎn)中卻說 這允許主 goroutine 繼續(xù)執(zhí)行,而不會(huì)被阻塞 呢?

解答:

先梳理一下代碼的執(zhí)行過程 :

  • done := make(chan struct{})

    • 創(chuàng)建一個(gè)無緩沖的未關(guān)閉的通道 done。
    • 這個(gè)通道用來通知主 goroutine 某個(gè)操作已完成。
  • go func() { ... }()

    • 啟動(dòng)一個(gè)新的 goroutine,并在這個(gè) goroutine 中執(zhí)行匿名函數(shù)。
    • 匿名函數(shù)會(huì)執(zhí)行具體的操作,然后關(guān)閉 done 通道。
  • close(done)

    • 在匿名函數(shù)中,當(dāng)操作完成時(shí),關(guān)閉 done 通道。
    • 關(guān)閉通道會(huì)通知所有等待該通道的接收者。
  • <-done

    • 主 goroutine 在這里等待 done 通道的關(guān)閉。
    • 這行代碼會(huì)阻塞當(dāng)前的主 goroutine,直到 done 通道被關(guān)閉。
    • 一旦 done 通道關(guān)閉,主 goroutine 將繼續(xù)執(zhí)行后續(xù)代碼。

進(jìn)一步解釋阻塞與非阻塞

  • 啟動(dòng) goroutine 是非阻塞操作:當(dāng) go func() { ... }() 這行代碼執(zhí)行時(shí),主 goroutine 會(huì)立即啟動(dòng)匿名函數(shù)做并行處理,然后繼續(xù)執(zhí)行主 goroutine 后面的代碼。也就是說,啟動(dòng) goroutine 本身不會(huì)阻塞主 goroutine。

  • 等待通道結(jié)果是阻塞操作:主 goroutine 接著執(zhí)行的 <-done 是一個(gè)阻塞操作。主 goroutine 將在 <-done 這一行阻塞,直到 done 通道被關(guān)閉。

為什么說“這允許主 goroutine 繼續(xù)執(zhí)行,而不會(huì)被阻塞”

前面的那句話實(shí)際是解釋在啟動(dòng) goroutine 時(shí),主 goroutine 是不會(huì)受阻塞的,這意味著它會(huì)繼續(xù)往下執(zhí)行 <-done 這一行代碼。但當(dāng)執(zhí)行到 <-done 時(shí),確實(shí)會(huì)阻塞,等待 done 通道被關(guān)閉。

這樣做的關(guān)鍵目的是為了同步 goroutine 的執(zhí)行:

  • 啟動(dòng) goroutine 讓它去做一些獨(dú)立的工作。
  • 主 goroutine 在啟動(dòng) goroutine 后等待它完成,通過 <-done 實(shí)現(xiàn)這一點(diǎn)。

這種模式非常適合在并發(fā)編程中進(jìn)行同步,確保主 goroutine 等待某個(gè) goroutine 完成實(shí)際工作后再繼續(xù)執(zhí)行。這種方法保證了并發(fā)程序的正確性和同步,并且避免了 goroutine 泄露(即 goroutine 執(zhí)行完任務(wù)后實(shí)際退出)。

7.解釋一下通道的selcet語句:

select是Go語言中的一個(gè)控制結(jié)構(gòu),專門用于處理多個(gè)通道操作。它的作用類似于switch語句,但是專門針對通道操作設(shè)計(jì)。

以下是select語句的詳細(xì)解析:

  • 基本語法:
select {
case <-ch1:
    // 如果可以從ch1接收數(shù)據(jù),執(zhí)行這里的代碼
case x := <-ch2:
    // 如果可以從ch2接收數(shù)據(jù),將數(shù)據(jù)賦值給x,然后執(zhí)行這里的代碼
case ch3 <- y:
    // 如果可以向ch3發(fā)送數(shù)據(jù)y,執(zhí)行這里的代碼
default:
    // 如果上面的case都沒有準(zhǔn)備好,執(zhí)行這里的代碼
}
  • 工作原理:
  • select會(huì)同時(shí)監(jiān)聽所有case語句中的通道操作。
  • 如果多個(gè)通道同時(shí)準(zhǔn)備就緒,select會(huì)隨機(jī)選擇一個(gè)case執(zhí)行。
  • 如果沒有任何通道準(zhǔn)備就緒,且有default語句,就執(zhí)行default語句。
  • 如果沒有default語句,select將阻塞,直到某個(gè)通道可以操作。
  • 常見用法:

a. 非阻塞通道操作:

select {
case msg := <-ch:
    fmt.Println("Received:", msg)
default:
    fmt.Println("No message received")
}

b. 超時(shí)處理:

select {
case res := <-ch:
    fmt.Println("Received:", res)
case <-time.After(1 * time.Second):
    fmt.Println("Timeout")
}

c. 多通道監(jiān)聽:

for {
    select {
    case msg1 := <-ch1:
        fmt.Println("ch1 received:", msg1)
    case msg2 := <-ch2:
        fmt.Println("ch2 received:", msg2)
    case <-done:
        return
    }
}
  • 特殊情況:
  • 空select(沒有任何case)會(huì)永遠(yuǎn)阻塞:
select {}
  • 當(dāng)多個(gè)case同時(shí)就緒時(shí),Go運(yùn)行時(shí)會(huì)偽隨機(jī)地選擇一個(gè)case執(zhí)行。

8.超時(shí)處理分析:

select {
case res := <-ch:
    fmt.Println("Received:", res)
case <-time.After(1 * time.Second):
    fmt.Println("Timeout")
}
  • select 語句:

    • select 是Go語言的一個(gè)控制結(jié)構(gòu),用于同時(shí)監(jiān)聽多個(gè)通道操作。
    • 它會(huì)阻塞,直到其中一個(gè)case可以執(zhí)行。
    • 如果多個(gè)case同時(shí)就緒,會(huì)隨機(jī)選擇一個(gè)執(zhí)行。
  • 第一個(gè) case:case res := <-ch:

    • 這個(gè)case嘗試從通道 ch 接收數(shù)據(jù)。
    • 如果 ch 中有數(shù)據(jù)可讀,這個(gè)case會(huì)被選中執(zhí)行。
    • res 變量會(huì)被賦值為從通道接收到的數(shù)據(jù)。
  • 第一個(gè) case 的執(zhí)行體:

    fmt.Println("Received:", res)
    
    • 如果從 ch 成功接收到數(shù)據(jù),將打印 “Received:” 以及接收到的數(shù)據(jù)。
  • 第二個(gè) case:case <-time.After(1 * time.Second):

    • time.After(1 * time.Second) 返回一個(gè)通道,這個(gè)通道將在1秒后發(fā)送一個(gè)值。
    • 這個(gè)case會(huì)在1秒后變?yōu)榭蓤?zhí)行狀態(tài)。
    • 如果在1秒內(nèi)第一個(gè)case沒有被觸發(fā),這個(gè)case就會(huì)被選中。
  • 第二個(gè) case 的執(zhí)行體:

    fmt.Println("Timeout")
    
    • 如果超過1秒還沒有從 ch 接收到數(shù)據(jù),將打印 “Timeout”。

整體邏輯:

  • 這個(gè)select語句同時(shí)等待兩個(gè)事件:從 ch 接收數(shù)據(jù)或1秒超時(shí)。
  • 如果在1秒內(nèi)從 ch 接收到數(shù)據(jù),程序會(huì)打印接收到的數(shù)據(jù)。
  • 如果1秒過去了還沒有從 ch 接收到數(shù)據(jù),就會(huì)觸發(fā)超時(shí),程序會(huì)打印超時(shí)消息。

使用場景:

  • 這種模式常用于需要設(shè)置超時(shí)的操作,例如:

    等待異步操作的結(jié)果

    網(wǎng)絡(luò)請求

    用戶輸入

    資源獲?。ㄈ鐢?shù)據(jù)庫查詢)

注意事項(xiàng):

  • ch 應(yīng)該是在其他地方定義的一個(gè)通道,可能由另一個(gè)goroutine寫入數(shù)據(jù)。
  • 1秒的超時(shí)時(shí)間是一個(gè)示例,實(shí)際使用時(shí)應(yīng)根據(jù)具體需求調(diào)整。
  • 這種模式不會(huì)取消正在進(jìn)行的操作。如果需要取消操作,應(yīng)該考慮使用context包。
  • 如果 ch 是一個(gè)無緩沖通道,確保有其他goroutine在寫入數(shù)據(jù),否則可能導(dǎo)致goroutine泄漏。

優(yōu)點(diǎn):

  • 簡潔而強(qiáng)大,能夠優(yōu)雅地處理超時(shí)情況。
  • 避免程序因等待某個(gè)可能永遠(yuǎn)不會(huì)完成的操作而無限阻塞。

這種模式展示了Go語言在處理并發(fā)和超時(shí)問題時(shí)的優(yōu)雅和高效。它允許開發(fā)者輕松地實(shí)現(xiàn)非阻塞操作和超時(shí)控制,這在構(gòu)建可靠的并發(fā)系統(tǒng)時(shí)非常有用。

9.對注意事項(xiàng)中的第3點(diǎn)進(jìn)行分析:

這句話指出了使用 select 和 time.After 實(shí)現(xiàn)的超時(shí)模式的一個(gè)局限性,同時(shí)提供了一個(gè)更完善的解決方案。詳細(xì)解釋如下:

  • 局限性:
    使用 select 和 time.After 的超時(shí)模式只能檢測到超時(shí),但不能主動(dòng)取消或停止正在進(jìn)行的操作。如果超時(shí)發(fā)生,代碼會(huì)執(zhí)行超時(shí)分支,但原來的操作可能仍在后臺(tái)繼續(xù)執(zhí)行,這可能導(dǎo)致資源浪費(fèi)或其他問題。

  • context 包的作用:
    Go 的 context 包提供了一種優(yōu)雅的方式來傳遞截止時(shí)間、取消信號(hào)或其他請求范圍的值across API邊界和進(jìn)程。使用 context,你可以:

    • 設(shè)置超時(shí)
    • 取消操作
    • 傳遞請求范圍的值
  • 使用 context 的優(yōu)勢:

    • 可以在超時(shí)發(fā)生時(shí)主動(dòng)取消正在進(jìn)行的操作
    • 可以在多個(gè) goroutine 之間協(xié)調(diào)取消操作
    • 提供了一種標(biāo)準(zhǔn)的方式來處理取消和超時(shí)

示例對比:

不使用 context 的版本(無法取消操作):

func doOperation() <-chan int {
    resultChan := make(chan int)
    go func() {
        // 假設(shè)這是一個(gè)耗時(shí)操作
        time.Sleep(2 * time.Second)
        resultChan <- 42
    }()
    return resultChan
}

func main() {
    select {
    case result := <-doOperation():
        fmt.Println("Result:", result)
    case <-time.After(1 * time.Second):
        fmt.Println("Timeout")
        // 操作仍在后臺(tái)繼續(xù)執(zhí)行
    }
}

使用 context 的版本(可以取消操作):

func doOperation(ctx context.Context) <-chan int {
    resultChan := make(chan int)
    go func() {
        select {
        case <-time.After(2 * time.Second):
            resultChan <- 42
        case <-ctx.Done():
            fmt.Println("Operation cancelled")
            return
        }
    }()
    return resultChan
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()

    select {
    case result := <-doOperation(ctx):
        fmt.Println("Result:", result)
    case <-ctx.Done():
        fmt.Println("Timeout")
        // 操作被取消,不會(huì)繼續(xù)執(zhí)行
    }
}

在使用 context 的版本中,如果超時(shí)發(fā)生,操作會(huì)被主動(dòng)取消,避免了資源浪費(fèi)。這種方法更加靈活和強(qiáng)大,特別是在處理復(fù)雜的并發(fā)場景時(shí)。

總之,雖然 select 和 time.After 的模式簡單直接,但在需要真正取消操作或在多個(gè) goroutine 間協(xié)調(diào)的場景中,使用 context 包是更好的選擇。

10.對使用 context 的版本的代碼進(jìn)行分析:

1.context是什么

Context 是 Go 語言中用于跨 API 邊界和進(jìn)程間傳遞截止時(shí)間、取消信號(hào)以及其他請求作用域值的一個(gè)標(biāo)準(zhǔn)包。它是 Go 1.7 版本引入的,主要用于解決 goroutine 管理和請求取消的問題。以下是關(guān)于 context 的幾個(gè)關(guān)鍵點(diǎn):

  • 主要用途:

    • 傳遞取消信號(hào)
    • 傳遞截止時(shí)間
    • 傳遞請求作用域的值
    • 跨 API 邊界的數(shù)據(jù)傳遞

核心接口:
Context 接口定義如下:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

主要功能:

  • 取消操作:通過 Done() 方法返回一個(gè) channel,當(dāng) context 被取消時(shí),這個(gè) channel 會(huì)被關(guān)閉。
  • 設(shè)置截止時(shí)間:可以為操作設(shè)置一個(gè)截止時(shí)間。
  • 傳值:可以攜帶請求作用域的值,用于跨函數(shù)、跨 API 調(diào)用傳遞數(shù)據(jù)。

常用函數(shù):

  • context.Background():返回一個(gè)空的 Context,通常用作頂層 Context。
  • context.TODO():當(dāng)不確定使用哪種 Context 時(shí)使用。
  • context.WithCancel(parent Context):創(chuàng)建一個(gè)可取消的子 Context。
  • context.WithDeadline(parent Context, deadline time.Time):創(chuàng)建一個(gè)具有截止時(shí)間的子 Context。
  • context.WithTimeout(parent Context, timeout time.Duration):創(chuàng)建一個(gè)具有超時(shí)時(shí)間的子 Context。
  • context.WithValue(parent Context, key, val interface{}):創(chuàng)建一個(gè)攜帶鍵值對的子 Context。

使用場景:

  • HTTP 請求的上下文傳遞
  • 數(shù)據(jù)庫查詢的超時(shí)控制
  • 多個(gè) goroutine 之間的協(xié)調(diào)和取消
  • 跨服務(wù)調(diào)用的上下文傳遞

最佳實(shí)踐:

  • 將 Context 作為函數(shù)的第一個(gè)參數(shù)傳遞。
  • 不要將 nil 作為 Context 類型的參數(shù)值,如果不確定使用什么,就使用 context.TODO()。
  • Context 應(yīng)該是只讀的,不要修改它。
  • 同一個(gè) Context 可以傳遞給在不同 goroutine 中運(yùn)行的函數(shù)。

優(yōu)點(diǎn):

  • 提供了一種標(biāo)準(zhǔn)的方式來處理取消和超時(shí)。
  • 使得跨 API 和進(jìn)程邊界的請求作用域數(shù)據(jù)傳遞變得簡單。
  • 有助于防止資源泄漏。

注意事項(xiàng):

  • Context 應(yīng)該貫穿整個(gè)請求的生命周期。
  • 不要將 Context 存儲(chǔ)在結(jié)構(gòu)體中,而應(yīng)該顯式地傳遞。
  • 取消操作是建議性的,不是強(qiáng)制性的。被取消的函數(shù)應(yīng)該盡快返回,但可能需要一些清理工作。

Context 包的引入大大簡化了在 Go 程序中處理取消、超時(shí)和跨調(diào)用邊界傳值的復(fù)雜性,是構(gòu)建健壯的并發(fā)和分布式系統(tǒng)的重要工具。

2.對ctx.Done()的分析:

ctx.Done() 是 Context 接口中的一個(gè)重要方法,它返回一個(gè)只讀的 channel(<-chan struct{})。這個(gè)方法在處理 context 的取消和超時(shí)時(shí)非常關(guān)鍵。以下是關(guān)于 ctx.Done() 的詳細(xì)解釋:

功能:

  • 返回一個(gè) channel,當(dāng) context 被取消或到達(dá)截止時(shí)間時(shí),這個(gè) channel 會(huì)被關(guān)閉。
  • 用于通知相關(guān)的 goroutine context 已經(jīng)結(jié)束,應(yīng)該停止當(dāng)前操作。

返回值:

  • 類型為 <-chan struct{},這是一個(gè)只讀的 channel。
  • 當(dāng) channel 關(guān)閉時(shí),讀取操作會(huì)立即返回一個(gè)零值(對于 struct{} 類型來說就是空結(jié)構(gòu)體)。

使用場景:

  • 在 select 語句中監(jiān)聽 context 的取消信號(hào)。
  • 在長時(shí)間運(yùn)行的操作中周期性檢查是否應(yīng)該停止。
  • 協(xié)調(diào)多個(gè) goroutine 的取消操作。

典型用法:

select {
case &lt;-ctx.Done():
    // Context 已被取消,執(zhí)行清理操作
    return ctx.Err()
case &lt;-someOtherChannel:
    // 處理其他情況
}

工作原理:

  • 當(dāng) context 被取消(通過調(diào)用 cancel 函數(shù))或達(dá)到截止時(shí)間時(shí),Done() 返回的 channel 會(huì)被關(guān)閉。
  • channel 的關(guān)閉會(huì)立即解除所有等待在這個(gè) channel 上的 goroutine 的阻塞狀態(tài)。

注意事項(xiàng):

  • ctx.Done() 本身不會(huì)阻塞,它只是返回一個(gè) channel。
  • 讀取這個(gè) channel(如 <-ctx.Done())會(huì)阻塞,直到 channel 被關(guān)閉。
  • 如果 context 永遠(yuǎn)不會(huì)被取消(如使用 context.Background()),則 Done() 返回的 channel 永遠(yuǎn)不會(huì)被關(guān)閉。

與 ctx.Err() 的關(guān)系:

  • 當(dāng) ctx.Done() 返回的 channel 被關(guān)閉時(shí),ctx.Err() 會(huì)返回一個(gè)非空錯(cuò)誤,表明 context 被取消的原因(如 “context canceled” 或 “context deadline exceeded”)。

最佳實(shí)踐:

  • 在處理長時(shí)間運(yùn)行的操作時(shí),定期檢查 ctx.Done()。
  • 結(jié)合使用 select 語句和 ctx.Done() 來實(shí)現(xiàn)可取消的操作。
  • 在返回時(shí),通常應(yīng)該檢查并返回 ctx.Err(),以傳播取消的原因。

ctx.Done() 是實(shí)現(xiàn)優(yōu)雅取消和超時(shí)處理的關(guān)鍵機(jī)制,它允許 Go 程序以非阻塞的方式響應(yīng)取消信號(hào),從而編寫出更加健壯和響應(yīng)式的并發(fā)代碼。

3.代碼分析:

  • doOperation 函數(shù):
func doOperation(ctx context.Context) <-chan int {
    resultChan := make(chan int)
    go func() {
        select {
        case <-time.After(2 * time.Second):
            resultChan <- 42
        case <-ctx.Done():
            fmt.Println("Operation cancelled")
            return
        }
    }()
    return resultChan
}

函數(shù)接收一個(gè) context.Context 參數(shù),返回一個(gè)只讀的整數(shù)通道。

創(chuàng)建一個(gè) resultChan 通道用于返回結(jié)果。

啟動(dòng)一個(gè) goroutine 執(zhí)行實(shí)際的操作:

  • 使用 select 語句同時(shí)等待兩個(gè)事件:

    2秒后的定時(shí)器觸發(fā)(模擬耗時(shí)操作)

    context 被取消

  • 如果 2 秒后定時(shí)器先觸發(fā),將 42 發(fā)送到 resultChan。
  • 如果 context 被取消,打印取消消息并返回,不發(fā)送結(jié)果。
  • 立即返回 resultChan,不等待 goroutine 完成。

main 函數(shù):

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()

    select {
    case result := <-doOperation(ctx):
        fmt.Println("Result:", result)
    case <-ctx.Done():
        fmt.Println("Timeout")
    }
}

創(chuàng)建一個(gè)帶有 1 秒超時(shí)的 context:

  • context.Background() 創(chuàng)建一個(gè)空的 context。
  • context.WithTimeout 基于背景 context 創(chuàng)建一個(gè) 1 秒后會(huì)自動(dòng)取消的新 context。
  • cancel 是一個(gè)函數(shù),可以手動(dòng)取消 context(這里通過 defer 確保在函數(shù)結(jié)束時(shí)調(diào)用)。

使用 select 語句等待兩個(gè)可能的結(jié)果:

從 doOperation 返回的通道接收結(jié)果。

context 超時(shí)或被取消(通過 ctx.Done() 通道)。

執(zhí)行流程:

  • main 函數(shù)創(chuàng)建一個(gè) 1 秒超時(shí)的 context。
  • 調(diào)用 doOperation,傳入這個(gè) context。
  • doOperation 啟動(dòng)一個(gè) goroutine,模擬一個(gè)需要 2 秒完成的操作。
  • main 函數(shù)的 select 等待結(jié)果或超時(shí)。
  • 由于 context 設(shè)置為 1 秒后超時(shí),而操作需要 2 秒:
    • context 會(huì)在 1 秒后被取消。
    • main 函數(shù)中的 <-ctx.Done() 會(huì)先觸發(fā)。
    • “Timeout” 會(huì)被打印。
  • 同時(shí),在 doOperation 的 goroutine 中,<-ctx.Done() 也會(huì)觸發(fā)。
    • “Operation cancelled” 會(huì)被打印。
    • goroutine 會(huì)提前退出,不會(huì)發(fā)送結(jié)果。

關(guān)鍵點(diǎn):

  • 使用 context 可以在多個(gè) goroutine 之間協(xié)調(diào)取消操作。
  • 這種模式允許優(yōu)雅地處理超時(shí),并確保資源不會(huì)被不必要地占用。
  • defer cancel() 確保即使發(fā)生 panic,context 也會(huì)被正確取消。
  • 這個(gè)例子展示了如何在超時(shí)發(fā)生時(shí)取消正在進(jìn)行的操作,這是簡單的 select + time.After 模式所不能實(shí)現(xiàn)的。

這種模式在處理網(wǎng)絡(luò)請求、數(shù)據(jù)庫查詢或其他可能需要超時(shí)控制的耗時(shí)操作時(shí)特別有用。

11.可能疑惑的問題:

為什么main函數(shù)中的<-ctx.Done()先執(zhí)行,而不是doOperation函數(shù)中的<-ctx.Done()先執(zhí)行 ?在這個(gè)具體的例子中,main 函數(shù)中的 <-ctx.Done() 會(huì)先執(zhí)行。這是因?yàn)椋?/p>

  • 時(shí)間設(shè)置:

    • main 函數(shù)中創(chuàng)建的 context 設(shè)置了 1 秒的超時(shí):
      ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    • doOperation 函數(shù)模擬的操作需要 2 秒才能完成:
      case <-time.After(2 * time.Second):
  • 執(zhí)行順序:

    • main 函數(shù)調(diào)用 doOperation 并立即進(jìn)入 select 語句等待。
    • doOperation 啟動(dòng)一個(gè)新的 goroutine 并立即返回 channel。
    • main 函數(shù)和 doOperation 中的 goroutine 并行執(zhí)行。
  • 超時(shí)觸發(fā):

    • 1 秒后,main 函數(shù)中的 context 達(dá)到超時(shí)時(shí)間。
    • context 被取消,這會(huì)同時(shí)關(guān)閉 main 函數(shù)和 doOperation 中的 ctx.Done() channel。
  • 結(jié)果:

    • main 函數(shù)中的 <-ctx.Done() 分支會(huì)先被觸發(fā),因?yàn)樗诘却?1 秒的超時(shí)。
    • 緊接著,doOperation 中的 <-ctx.Done() 也會(huì)被觸發(fā)。
  • 輸出順序:

    • 你會(huì)看到 main 函數(shù)先打印 “Timeout”。
    • 然后 doOperation 的 goroutine 會(huì)打印 “Operation cancelled”。

這種行為展示了 context 的一個(gè)重要特性:當(dāng)一個(gè) context 被取消時(shí),它會(huì)立即通知所有使用該 context 的 goroutine。這允許程序在不同的部分協(xié)調(diào)取消操作,即使這些部分在不同的 goroutine 中運(yùn)行。

需要注意的是,雖然 main 函數(shù)中的 <-ctx.Done() 先執(zhí)行,但兩者執(zhí)行的時(shí)間差通常非常小,幾乎是同時(shí)的。這個(gè)順序主要是由于 main 函數(shù)直接等待 context 的取消,而 doOperation 中還有一個(gè)額外的 select 語句。

  • 注意事項(xiàng):
  • select語句不會(huì)按照case的順序進(jìn)行選擇。
  • 空的select{}會(huì)永遠(yuǎn)阻塞。
  • 在循環(huán)中使用select時(shí),要注意避免CPU占用過高(可以在default中加入短暫的睡眠)。
  • 使用select實(shí)現(xiàn)超時(shí)或取消操作時(shí),記得正確關(guān)閉相關(guān)的goroutine和資源。

select語句是Go并發(fā)編程中的一個(gè)強(qiáng)大工具,它允許你同時(shí)處理多個(gè)通道操作,實(shí)現(xiàn)非阻塞I/O、超時(shí)處理、優(yōu)雅退出等復(fù)雜的并發(fā)控制流程。深入理解和靈活運(yùn)用select可以幫助你編寫更加高效和健壯的并發(fā)程序。

到此這篇關(guān)于Go語言協(xié)程通道使用的問題小結(jié)的文章就介紹到這了,更多相關(guān)Go語言協(xié)程通道內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go常用標(biāo)準(zhǔn)庫之fmt的簡介與使用詳解

    Go常用標(biāo)準(zhǔn)庫之fmt的簡介與使用詳解

    fmt 是 Go 語言中的一個(gè)常用標(biāo)準(zhǔn)庫,它用于格式化輸入和輸出數(shù)據(jù),這篇文章主要為大家介紹了fmt的基本使用,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-10-10
  • Golang Copier入門到入坑探究

    Golang Copier入門到入坑探究

    這篇文章主要為大家介紹了Golang Copier入門到入坑探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Go語言中的IO操作及Flag包的用法

    Go語言中的IO操作及Flag包的用法

    這篇文章介紹了Go語言中的IO操作及Flag包的用法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • go 判斷兩個(gè) slice/struct/map 是否相等的實(shí)例

    go 判斷兩個(gè) slice/struct/map 是否相等的實(shí)例

    這篇文章主要介紹了go 判斷兩個(gè) slice/struct/map 是否相等的實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 一文帶你了解GO語言中方法的應(yīng)用

    一文帶你了解GO語言中方法的應(yīng)用

    GO?語言中的方法實(shí)際上和函數(shù)是類似的,只不過在函數(shù)的基礎(chǔ)上多了一個(gè)參數(shù),這篇文章主要為大家介紹一下GO語言中方法的應(yīng)用,需要的可以參考下
    2023-09-09
  • 解決go在函數(shù)退出后子協(xié)程的退出問題

    解決go在函數(shù)退出后子協(xié)程的退出問題

    這篇文章主要介紹了解決go在函數(shù)退出后子協(xié)程的退出問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • GoFrame?gmap遍歷hashmap?listmap?treemap使用技巧

    GoFrame?gmap遍歷hashmap?listmap?treemap使用技巧

    這篇文章主要為大家介紹了GoFrame?gmap遍歷hashmap?listmap?treemap使用技巧的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • 詳解golang 模板(template)的常用基本語法

    詳解golang 模板(template)的常用基本語法

    這篇文章主要介紹了詳解golang 模板(template)的常用基本語法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • Go動(dòng)態(tài)調(diào)用函數(shù)的實(shí)例教程

    Go動(dòng)態(tài)調(diào)用函數(shù)的實(shí)例教程

    本文主要介紹了Go動(dòng)態(tài)調(diào)用函數(shù)的實(shí)例教程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • 淺析Go語言如何在終端里實(shí)現(xiàn)倒計(jì)時(shí)

    淺析Go語言如何在終端里實(shí)現(xiàn)倒計(jì)時(shí)

    這篇文章主要為大家詳細(xì)介紹了Go語言中是如何在終端里實(shí)現(xiàn)倒計(jì)時(shí)的,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-03-03

最新評論