国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

首頁 后端開發(fā) Golang Go Singleflight 融入您的代碼中,而不是您的數(shù)據(jù)庫中

Go Singleflight 融入您的代碼中,而不是您的數(shù)據(jù)庫中

Nov 05, 2024 pm 12:27 PM

原文發(fā)布在VictoriaMetrics博客上:https://victoriametrics.com/blog/go-singleflight/

這篇文章是關(guān)于 Go 中處理并發(fā)的系列文章的一部分:

  • Gosync.Mutex:正常和饑餓模式
  • Gosync.WaitGroup 和對齊問題
  • Gosync.Pool 及其背后的機制
  • Gosync.Cond,最被忽視的同步機制
  • Gosync.Map:適合正確工作的正確工具
  • Go Sync.Once 很簡單...真的嗎?
  • Go Singleflight 融入您的代碼,而不是您的數(shù)據(jù)庫(我們在這里)

Go Singleflight Melts in Your Code, Not in Your?DB

Go Singleflight 融入您的代碼,而不是您的數(shù)據(jù)庫

因此,當(dāng)您同時收到多個請求相同的數(shù)據(jù)時,默認(rèn)行為是每個請求都會單獨訪問數(shù)據(jù)庫以獲取相同的信息。這意味著您最終會多次執(zhí)行相同的查詢,老實說,這效率很低。

Go Singleflight Melts in Your Code, Not in Your?DB

多個相同的請求到達(dá)數(shù)據(jù)庫

它最終會給數(shù)據(jù)庫帶來不必要的負(fù)載,這可能會減慢一切,但有一種方法可以解決這個問題。

這個想法是只有第一個請求實際上才會發(fā)送到數(shù)據(jù)庫。其余請求等待第一個請求完成。一旦數(shù)據(jù)從初始請求返回,其他請求就會得到相同的結(jié)果 - 不需要額外的查詢。

Go Singleflight Melts in Your Code, Not in Your?DB

singleflight 如何抑制重復(fù)請求

那么,現(xiàn)在您已經(jīng)很清楚這篇文章的內(nèi)容了,對吧?

單程航班

Go 中的 singleflight 包是專門為處理我們剛才討論的問題而構(gòu)建的。請注意,它不是標(biāo)準(zhǔn)庫的一部分,但由 Go 團(tuán)隊維護(hù)和開發(fā)。

singleflight 的作用是確保只有一個 goroutine 實際運行該操作,例如從數(shù)據(jù)庫獲取數(shù)據(jù)。它只允許在任何給定時刻對同一條數(shù)據(jù)(稱為“密鑰”)執(zhí)行一次“進(jìn)行中”(正在進(jìn)行的)操作。

因此,如果其他 goroutine 在該操作仍在進(jìn)行時請求相同的數(shù)據(jù)(相同的鍵),它們只會等待。然后,當(dāng)?shù)谝粋€操作完成時,所有其他操作都會得到相同的結(jié)果,而無需再次運行該操作。

好了,說得夠多了,讓我們深入了解一下 singleflight 的實際工作原理:

var callCount atomic.Int32
var wg sync.WaitGroup

// Simulate a function that fetches data from a database
func fetchData() (interface{}, error) {
    callCount.Add(1)
    time.Sleep(100 * time.Millisecond)
    return rand.Intn(100), nil
}

// Wrap the fetchData function with singleflight
func fetchDataWrapper(g *singleflight.Group, id int) error {
    defer wg.Done()

    time.Sleep(time.Duration(id) * 40 * time.Millisecond)
    v, err, shared := g.Do("key-fetch-data", fetchData)
    if err != nil {
        return err
    }

    fmt.Printf("Goroutine %d: result: %v, shared: %v\n", id, v, shared)
    return nil
}

func main() {
    var g singleflight.Group

    // 5 goroutines to fetch the same data
    const numGoroutines = 5
    wg.Add(numGoroutines)

    for i := 0; i < numGoroutines; i++ {
        go fetchDataWrapper(&g, i)
    }

    wg.Wait()
    fmt.Printf("Function was called %d times\n", callCount.Load())
}

// Output:
// Goroutine 0: result: 90, shared: true
// Goroutine 2: result: 90, shared: true
// Goroutine 1: result: 90, shared: true
// Goroutine 3: result: 13, shared: true
// Goroutine 4: result: 13, shared: true
// Function was called 2 times

這里發(fā)生了什么:

我們正在模擬這樣的情況:5 個 goroutine 幾乎同時嘗試獲取相同的數(shù)據(jù),間隔 60 毫秒。為了簡單起見,我們使用隨機數(shù)來模擬從數(shù)據(jù)庫中獲取的數(shù)據(jù)。

使用 singleflight.Group,我們確保只有第一個 goroutine 實際運行 fetchData(),其余的 goroutine 等待結(jié)果。

行 v, err, shared := g.Do("key-fetch-data", fetchData) 分配一個唯一的鍵 ("key-fetch-data") 來跟蹤這些請求。因此,如果另一個 goroutine 請求相同的鍵,而第一個 goroutine 仍在獲取數(shù)據(jù),它會等待結(jié)果而不是開始新的調(diào)用。

Go Singleflight Melts in Your Code, Not in Your?DB

單次飛行演示

一旦第一個調(diào)用完成,任何等待的 goroutine 都會得到相同的結(jié)果,正如我們在輸出中看到的那樣。雖然我們有 5 個 goroutine 請求數(shù)據(jù),但 fetchData 只運行了兩次,這是一個巨大的提升。

共享標(biāo)志確認(rèn)結(jié)果已在多個 goroutine 之間重用。

“但是為什么第一個 goroutine 的共享標(biāo)志為 true?我以為只有等待的 goroutine 才會共享 == true?”

是的,如果您認(rèn)為只有等待的 goroutine 應(yīng)該共享 == true,這可能會感覺有點違反直覺。

問題是,g.Do 中的共享變量告訴您??結(jié)果是否在多個調(diào)用者之間共享。它基本上是在說:“嘿,這個結(jié)果被多個調(diào)用者使用了?!边@與誰運行該函數(shù)無關(guān),它只是一個信號,表明結(jié)果在多個 goroutine 之間重用。

“我有緩存,為什么我需要單次飛行?”

簡短的答案是:緩存和 singleflight 解決不同的問題,而且它們實際上可以很好地協(xié)同工作。

在使用外部緩存(如 Redis 或 Memcached)的設(shè)置中,singleflight 增加了額外的保護(hù)層,不僅為您的數(shù)據(jù)庫,也為緩存本身。

Go Singleflight Melts in Your Code, Not in Your?DB

Singleflight 與緩存系統(tǒng)一起工作

此外,singleflight 有助于防止緩存未命中風(fēng)暴(有時稱為“緩存踩踏”)。

通常,當(dāng)請求請求數(shù)據(jù)時,如果數(shù)據(jù)在緩存中,那就太好了 - 這是緩存命中。如果數(shù)據(jù)不在緩存中,則為緩存未命中。假設(shè)在重建緩存之前有 10,000 個請求同時到達(dá)系統(tǒng),數(shù)據(jù)庫可能會突然同時受到 10,000 個相同查詢的沖擊。

在此高峰期間,singleflight 確保這 10,000 個請求中只有一個真正到達(dá)數(shù)據(jù)庫。

但是稍后,在內(nèi)部實現(xiàn)部分,我們將看到 singleflight 使用全局鎖來保護(hù)正在進(jìn)行的調(diào)用的映射,這可能成為每個 goroutine 的單點爭用。這可能會減慢速度,尤其是在處理高并發(fā)時。

下面的模型可能更適合具有多個 CPU 的機器:

Go Singleflight Melts in Your Code, Not in Your?DB

緩存未命中時的單次飛行

在此設(shè)置中,我們僅在發(fā)生緩存未命中時使用 singleflight。

單次航班運營

要使用 singleflight,您首先創(chuàng)建一個 Group 對象,它是跟蹤鏈接到特定鍵的正在進(jìn)行的函數(shù)調(diào)用的核心結(jié)構(gòu)。

它有兩個有助于防止重復(fù)調(diào)用的關(guān)鍵方法:

  • group.Do(key, func):運行函數(shù),同時抑制重復(fù)請求。當(dāng)您調(diào)用 Do 時,您傳入一個鍵和一個函數(shù),如果該鍵沒有發(fā)生其他執(zhí)行,則該函數(shù)將運行。如果同一個鍵已經(jīng)有一個執(zhí)行正在進(jìn)行,您的調(diào)用將阻塞,直到第一個執(zhí)行完成并返回相同的結(jié)果。
  • group.DoChan(key, func):與 group.Do 類似,但它不是阻塞,而是為您提供一個通道(

我們已經(jīng)在演示中了解了如何使用 g.Do(),讓我們看看如何使用經(jīng)過修改的包裝函數(shù)的 g.DoChan() :

var callCount atomic.Int32
var wg sync.WaitGroup

// Simulate a function that fetches data from a database
func fetchData() (interface{}, error) {
    callCount.Add(1)
    time.Sleep(100 * time.Millisecond)
    return rand.Intn(100), nil
}

// Wrap the fetchData function with singleflight
func fetchDataWrapper(g *singleflight.Group, id int) error {
    defer wg.Done()

    time.Sleep(time.Duration(id) * 40 * time.Millisecond)
    v, err, shared := g.Do("key-fetch-data", fetchData)
    if err != nil {
        return err
    }

    fmt.Printf("Goroutine %d: result: %v, shared: %v\n", id, v, shared)
    return nil
}

func main() {
    var g singleflight.Group

    // 5 goroutines to fetch the same data
    const numGoroutines = 5
    wg.Add(numGoroutines)

    for i := 0; i < numGoroutines; i++ {
        go fetchDataWrapper(&g, i)
    }

    wg.Wait()
    fmt.Printf("Function was called %d times\n", callCount.Load())
}

// Output:
// Goroutine 0: result: 90, shared: true
// Goroutine 2: result: 90, shared: true
// Goroutine 1: result: 90, shared: true
// Goroutine 3: result: 13, shared: true
// Goroutine 4: result: 13, shared: true
// Function was called 2 times
// Wrap the fetchData function with singleflight using DoChan
func fetchDataWrapper(g *singleflight.Group, id int) error {
    defer wg.Done()

    ch := g.DoChan("key-fetch-data", fetchData)

    res := <-ch
    if res.Err != nil {
        return res.Err
    }

    fmt.Printf("Goroutine %d: result: %v, shared: %v\n", id, res.Val, res.Shared)
    return nil
}

說實話,這里使用 DoChan() 與 Do() 相比并沒有太大變化,因為我們?nèi)栽诘却ǖ澜邮詹僮?(

DoChan() 的閃光點是當(dāng)你想要啟動一個操作并在不阻塞 goroutine 的情況下執(zhí)行其他操作時。例如,您可以使用通道更干凈地處理超時或取消:

package singleflight

type Result struct {
    Val    interface{}
    Err    error
    Shared bool
}

此示例還提出了您在現(xiàn)實場景中可能遇到的一些問題:

  • 由于網(wǎng)絡(luò)響應(yīng)緩慢、數(shù)據(jù)庫無響應(yīng)等原因,第一個 Goroutine 可能會比預(yù)期花費更長的時間。在這種情況下,所有其他等待的 Goroutine 的卡住時間都會比您希望的要長。超時可以在這里提供幫助,但任何新請求仍然會在第一個請求之后等待。
  • 您獲取的數(shù)據(jù)可能會經(jīng)常更改,因此當(dāng)?shù)谝粋€請求完成時,結(jié)果可能已經(jīng)過時。這意味著我們需要一種方法來使密鑰無效并觸發(fā)新的執(zhí)行。

是的,singleflight 提供了一種使用 group.Forget(key) 方法來處理此類情況的方法,它可以讓您放棄正在進(jìn)行的執(zhí)行。

Forget() 方法從跟蹤正在進(jìn)行的函數(shù)調(diào)用的內(nèi)部映射中刪除一個鍵。這有點像“使鍵無效”,因此如果您使用該鍵再次調(diào)用 g.Do(),它將像新請求一樣執(zhí)行該函數(shù),而不是等待上一次執(zhí)行完成。

讓我們更新示例以使用 Forget() 并查看該函數(shù)實際被調(diào)用了多少次:

var callCount atomic.Int32
var wg sync.WaitGroup

// Simulate a function that fetches data from a database
func fetchData() (interface{}, error) {
    callCount.Add(1)
    time.Sleep(100 * time.Millisecond)
    return rand.Intn(100), nil
}

// Wrap the fetchData function with singleflight
func fetchDataWrapper(g *singleflight.Group, id int) error {
    defer wg.Done()

    time.Sleep(time.Duration(id) * 40 * time.Millisecond)
    v, err, shared := g.Do("key-fetch-data", fetchData)
    if err != nil {
        return err
    }

    fmt.Printf("Goroutine %d: result: %v, shared: %v\n", id, v, shared)
    return nil
}

func main() {
    var g singleflight.Group

    // 5 goroutines to fetch the same data
    const numGoroutines = 5
    wg.Add(numGoroutines)

    for i := 0; i < numGoroutines; i++ {
        go fetchDataWrapper(&g, i)
    }

    wg.Wait()
    fmt.Printf("Function was called %d times\n", callCount.Load())
}

// Output:
// Goroutine 0: result: 90, shared: true
// Goroutine 2: result: 90, shared: true
// Goroutine 1: result: 90, shared: true
// Goroutine 3: result: 13, shared: true
// Goroutine 4: result: 13, shared: true
// Function was called 2 times

Goroutine 0 和 Goroutine 1 都使用相同的鍵(“key-fetch-data”)調(diào)用 Do(),它們的請求合并為一次執(zhí)行,結(jié)果在兩個 Goroutine 之間共享。

Goroutine 2,另一方面,在運行 Do() 之前調(diào)用 Forget()。這會清除與“key-fetch-data”相關(guān)的任何先前結(jié)果,因此它會觸發(fā)該函數(shù)的新執(zhí)行。

總而言之,雖然 singleflight 很有用,但它仍然可能存在一些邊緣情況,例如:

  • 如果第一個 goroutine 被阻塞的時間太長,所有等待它的其他 goroutine 也會被卡住。在這種情況下,使用超時上下文或帶有超時的 select 語句可能是更好的選擇。
  • 如果第一個請求返回錯誤或恐慌,相同的錯誤或恐慌將傳播到等待結(jié)果的所有其他 goroutine。

如果您已經(jīng)注意到我們討論過的所有問題,讓我們深入到下一部分來討論 singleflight 的實際工作原理。

單次飛行如何運作

通過使用singleflight,你可能已經(jīng)對它的內(nèi)部工作原理有了一個基本的了解,singleflight的整個實現(xiàn)只有大約150行代碼。

基本上,每個唯一的鍵都有一個管理其執(zhí)行的結(jié)構(gòu)。如果 goroutine 調(diào)用 Do() 并發(fā)現(xiàn) key 已經(jīng)存在,則該調(diào)用將被阻塞,直到第一次執(zhí)行完成,結(jié)構(gòu)如下:

// Wrap the fetchData function with singleflight using DoChan
func fetchDataWrapper(g *singleflight.Group, id int) error {
    defer wg.Done()

    ch := g.DoChan("key-fetch-data", fetchData)

    res := <-ch
    if res.Err != nil {
        return res.Err
    }

    fmt.Printf("Goroutine %d: result: %v, shared: %v\n", id, res.Val, res.Shared)
    return nil
}

這里使用了兩個同步原語:

  • 組互斥鎖 (g.mu):該互斥鎖保護(hù)整個鍵映射,而不是每個鍵一個鎖,它確保添加或刪除鍵是線程安全的。
  • WaitGroup (g.call.wg):WaitGroup 用于等待與特定鍵關(guān)聯(lián)的第一個 goroutine 完成其工作。

這里我們將重點關(guān)注 group.Do() 方法,因為另一個方法 group.DoChan() 的工作方式類似。 group.Forget() 方法也很簡單,因為它只是從地圖中刪除鍵。

當(dāng)你調(diào)用 group.Do() 時,它所做的第一件事就是鎖定整個調(diào)用映射 (g.mu)。

“這對性能不是很不利嗎?”

是的,它可能并不適合每種情況下的性能(總是先進(jìn)行基準(zhǔn)測試),因為 singleflight 鎖定了整個密鑰。如果您的目標(biāo)是獲得更好的性能或大規(guī)模工作,一個好的方法是分片或分發(fā)密鑰。您可以將負(fù)載分散到多個組,而不是僅使用一個單一飛行組,有點像“多重飛行”

作為參考,請查看此存儲庫:shardedsingleflight。

現(xiàn)在,一旦獲得鎖,該組就會查看內(nèi)部映射 (g.m),如果已經(jīng)有對給定密鑰的正在進(jìn)行或已完成的調(diào)用。該地圖跟蹤任何正在進(jìn)行或已完成的工作,并將鍵映射到相應(yīng)的任務(wù)。

如果找到該鍵(另一個 goroutine 已經(jīng)在運行該任務(wù)),我們只需增加一個計數(shù)器(c.dups)來跟蹤重復(fù)請求,而不是開始新的調(diào)用。然后,goroutine 釋放鎖并通過在關(guān)聯(lián)的 WaitGroup 上調(diào)用 call.wg.Wait() 來等待原始任務(wù)完成。

當(dāng)原始任務(wù)完成時,這個 goroutine 會獲取結(jié)果并避免再次運行該任務(wù)。

var callCount atomic.Int32
var wg sync.WaitGroup

// Simulate a function that fetches data from a database
func fetchData() (interface{}, error) {
    callCount.Add(1)
    time.Sleep(100 * time.Millisecond)
    return rand.Intn(100), nil
}

// Wrap the fetchData function with singleflight
func fetchDataWrapper(g *singleflight.Group, id int) error {
    defer wg.Done()

    time.Sleep(time.Duration(id) * 40 * time.Millisecond)
    v, err, shared := g.Do("key-fetch-data", fetchData)
    if err != nil {
        return err
    }

    fmt.Printf("Goroutine %d: result: %v, shared: %v\n", id, v, shared)
    return nil
}

func main() {
    var g singleflight.Group

    // 5 goroutines to fetch the same data
    const numGoroutines = 5
    wg.Add(numGoroutines)

    for i := 0; i < numGoroutines; i++ {
        go fetchDataWrapper(&g, i)
    }

    wg.Wait()
    fmt.Printf("Function was called %d times\n", callCount.Load())
}

// Output:
// Goroutine 0: result: 90, shared: true
// Goroutine 2: result: 90, shared: true
// Goroutine 1: result: 90, shared: true
// Goroutine 3: result: 13, shared: true
// Goroutine 4: result: 13, shared: true
// Function was called 2 times

如果沒有其他 Goroutine 正在處理該鍵,則當(dāng)前 Goroutine 負(fù)責(zé)執(zhí)行該任務(wù)。

此時,我們創(chuàng)建一個新的調(diào)用對象,將其添加到映射中,并初始化其 WaitGroup。然后,我們解鎖互斥體并繼續(xù)通過輔助方法 g.doCall(c, key, fn) 自己執(zhí)行任務(wù)。當(dāng)任務(wù)完成時,任何等待的 goroutine 都會被 wg.Wait() 調(diào)用解除阻塞。

這里沒什么太瘋狂的,除了我們?nèi)绾翁幚礤e誤之外,還有三種可能的情況:

  • 如果函數(shù)發(fā)生恐慌,我們會捕獲它,將其包裝在一個恐慌錯誤中,然后引發(fā)恐慌。
  • 如果函數(shù)返回 errGoexit,我們調(diào)用 runtime.Goexit() 來正確退出 goroutine。
  • 如果這只是一個正常錯誤,我們會在調(diào)用時設(shè)置該錯誤。

這是輔助方法 g.doCall() 中事情開始變得更加聰明的地方。

“等等,什么是runtime.Goexit()?”

在深入代碼之前,讓我快速解釋一下,runtime.Goexit() 用于停止 goroutine 的執(zhí)行。

當(dāng) goroutine 調(diào)用 Goexit() 時,它會停止,并且任何延遲函數(shù)仍然按照后進(jìn)先出 (LIFO) 順序運行,就像正常情況一樣。它與恐慌類似,但有一些區(qū)別:

  • 它不會觸發(fā)恐慌,所以你無法用recover()捕獲它。
  • 只有調(diào)用 Goexit() 的 goroutine 被終止,所有其他 goroutine 都保持正常運行。

現(xiàn)在,這是一個有趣的怪癖(與我們的主題沒有直接關(guān)系,但值得一提)。如果你在主協(xié)程中調(diào)用runtime.Goexit()(比如在main()內(nèi)部),請檢查一下:

var callCount atomic.Int32
var wg sync.WaitGroup

// Simulate a function that fetches data from a database
func fetchData() (interface{}, error) {
    callCount.Add(1)
    time.Sleep(100 * time.Millisecond)
    return rand.Intn(100), nil
}

// Wrap the fetchData function with singleflight
func fetchDataWrapper(g *singleflight.Group, id int) error {
    defer wg.Done()

    time.Sleep(time.Duration(id) * 40 * time.Millisecond)
    v, err, shared := g.Do("key-fetch-data", fetchData)
    if err != nil {
        return err
    }

    fmt.Printf("Goroutine %d: result: %v, shared: %v\n", id, v, shared)
    return nil
}

func main() {
    var g singleflight.Group

    // 5 goroutines to fetch the same data
    const numGoroutines = 5
    wg.Add(numGoroutines)

    for i := 0; i < numGoroutines; i++ {
        go fetchDataWrapper(&g, i)
    }

    wg.Wait()
    fmt.Printf("Function was called %d times\n", callCount.Load())
}

// Output:
// Goroutine 0: result: 90, shared: true
// Goroutine 2: result: 90, shared: true
// Goroutine 1: result: 90, shared: true
// Goroutine 3: result: 13, shared: true
// Goroutine 4: result: 13, shared: true
// Function was called 2 times

發(fā)生的情況是 Goexit() 終止了主 goroutine,但如果還有其他 goroutine 仍在運行,程序會繼續(xù)運行,因為只要至少有一個 goroutine 處于活動狀態(tài),Go 運行時就會保持活動狀態(tài)。然而,一旦沒有剩下 goroutines,它就會因“no goroutine”錯誤而崩潰,這是一個有趣的小角落案例。

現(xiàn)在,回到我們的代碼,如果runtime.Goexit()僅終止當(dāng)前的goroutine并且無法被recover()捕獲,我們?nèi)绾螜z測它是否被調(diào)用?

關(guān)鍵在于,當(dāng)調(diào)用runtime.Goexit()時,其后面的任何代碼都不會被執(zhí)行。

// Wrap the fetchData function with singleflight using DoChan
func fetchDataWrapper(g *singleflight.Group, id int) error {
    defer wg.Done()

    ch := g.DoChan("key-fetch-data", fetchData)

    res := <-ch
    if res.Err != nil {
        return res.Err
    }

    fmt.Printf("Goroutine %d: result: %v, shared: %v\n", id, res.Val, res.Shared)
    return nil
}

在上面的情況下,調(diào)用runtime.Goexit()之后,normalReturn = true這一行永遠(yuǎn)不會被執(zhí)行。因此,在 defer 內(nèi)部,我們可以檢查 normalReturn 是否仍然為 false,以檢測是否調(diào)用了特殊方法。

下一步是確定任務(wù)是否出現(xiàn)恐慌。為此,我們使用recover()作為正常返回,盡管singleflight中的實際代碼有點微妙:

package singleflight

type Result struct {
    Val    interface{}
    Err    error
    Shared bool
}

這段代碼不是直接在recover塊內(nèi)設(shè)置recovered = true,而是通過在recover()塊之后將recovery設(shè)置為最后一行來獲得一點奇特的效果。

那么,為什么這會起作用?

當(dāng)調(diào)用runtime.Goexit()時,它會終止整個goroutine,就像panic()一樣。然而,如果panic()被恢復(fù),只有panic()和recover()之間的函數(shù)鏈被終止,而不是整個goroutine。

Go Singleflight Melts in Your Code, Not in Your?DB

singleflight中panic和runtime.Goexit()的處理

這就是為什么在包含recover()的defer之外設(shè)置recovered = true,它只在兩種情況下執(zhí)行:當(dāng)函數(shù)正常完成時或當(dāng)恐慌恢復(fù)時,但在調(diào)用runtime.Goexit()時不會執(zhí)行。

接下來,我們將討論如何處理每個案例。

func fetchDataWrapperWithTimeout(g *singleflight.Group, id int) error {
    defer wg.Done()

    ch := g.DoChan("key-fetch-data", fetchData)
    select {
    case res := <-ch:
        if res.Err != nil {
            return res.Err
        }
        fmt.Printf("Goroutine %d: result: %v, shared: %v\n", id, res.Val, res.Shared)
    case <-time.After(50 * time.Millisecond):
        return fmt.Errorf("timeout waiting for result")
    }

  return nil
}

如果任務(wù)在執(zhí)行過程中發(fā)生緊急情況,則會捕獲緊急情況并將其保存在 c.err 中作為緊急錯誤,其中包含緊急值和堆棧跟蹤。 singleflight 捕捉到恐慌并優(yōu)雅地清理,但它不會吞掉它,它會在處理其狀態(tài)后重新拋出恐慌。

這意味著執(zhí)行任務(wù)的 Goroutine(第一個開始執(zhí)行操作的 Goroutine)會發(fā)生恐慌,并且所有其他等待結(jié)果的 Goroutine 也會發(fā)生恐慌。

由于這種恐慌發(fā)生在開發(fā)人員的代碼中,因此我們有責(zé)任妥善處理它。

現(xiàn)在,我們?nèi)匀恍枰紤]一種特殊情況:當(dāng)其他 goroutine 使用 group.DoChan() 方法并通過通道等待結(jié)果時。在這種情況下,singleflight 不能在這些 goroutine 中發(fā)生恐慌。相反,它會執(zhí)行所謂的不可恢復(fù)的恐慌(gopanic(e)),這會使我們的應(yīng)用程序崩潰。

最后,如果任務(wù)調(diào)用了runtime.Goexit(),則不需要采取任何進(jìn)一步的操作,因為goroutine已經(jīng)處于關(guān)閉過程中,我們只是讓它發(fā)生而不干擾。

差不多就是這樣,除了我們討論過的特殊情況之外,沒有什么太復(fù)雜的。

保持聯(lián)系

大家好,我是 Phuong Le,VictoriaMetrics 的軟件工程師。上述寫作風(fēng)格注重清晰和簡單,以易于理解的方式解釋概念,即使它并不總是與學(xué)術(shù)精度完全一致。

如果您發(fā)現(xiàn)任何過時的內(nèi)容或有疑問,請隨時與我們聯(lián)系。您可以在 X(@func25) 上給我留言。

您可能感興趣的其他一些帖子:

  • Go I/O 讀取器、寫入器和動態(tài)數(shù)據(jù)。
  • Go 數(shù)組如何工作以及如何使用 For-Range
  • Go 中的切片:變大或回家
  • Go Maps 解釋:鍵值對實際上是如何存儲的
  • Golang Defer:從基礎(chǔ)到陷阱
  • 供應(yīng)商,或 go mod 供應(yīng)商:這是什么?

我們是誰

如果您想監(jiān)控您的服務(wù)、跟蹤指標(biāo)并了解一切的執(zhí)行情況,您可能需要查看 VictoriaMetrics。這是一種快速、開源且節(jié)省成本的方式來監(jiān)控您的基礎(chǔ)設(shè)施。

我們是 Gophers,熱愛研究、實驗和分享 Go 及其生態(tài)系統(tǒng)知識的愛好者。

以上是Go Singleflight 融入您的代碼中,而不是您的數(shù)據(jù)庫中的詳細(xì)內(nèi)容。更多信息請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本站聲明
本文內(nèi)容由網(wǎng)友自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,本站不承擔(dān)相應(yīng)法律責(zé)任。如您發(fā)現(xiàn)有涉嫌抄襲侵權(quán)的內(nèi)容,請聯(lián)系admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣服圖片

Undresser.AI Undress

Undresser.AI Undress

人工智能驅(qū)動的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用于從照片中去除衣服的在線人工智能工具。

Clothoff.io

Clothoff.io

AI脫衣機

Video Face Swap

Video Face Swap

使用我們完全免費的人工智能換臉工具輕松在任何視頻中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的代碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

功能強大的PHP集成開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級代碼編輯軟件(SublimeText3)

默認(rèn)情況下,GO靜態(tài)鏈接的含義是什么? 默認(rèn)情況下,GO靜態(tài)鏈接的含義是什么? Jun 19, 2025 am 01:08 AM

Go默認(rèn)將程序編譯為獨立二進(jìn)制文件,主要原因是靜態(tài)鏈接。1.部署更簡單:無需額外安裝依賴庫,可直接跨Linux發(fā)行版運行;2.二進(jìn)制體積更大:包含所有依賴導(dǎo)致文件尺寸增加,但可通過構(gòu)建標(biāo)志或壓縮工具優(yōu)化;3.更高的可預(yù)測性與安全性:避免外部庫版本變化帶來的風(fēng)險,增強穩(wěn)定性;4.運行靈活性受限:無法熱更新共享庫,需重新編譯部署以修復(fù)依賴漏洞。這些特性使Go適用于CLI工具、微服務(wù)等場景,但在存儲受限或依賴集中管理的環(huán)境中需權(quán)衡取舍。

在沒有C中的手動內(nèi)存管理的情況下,如何確保內(nèi)存安全性? 在沒有C中的手動內(nèi)存管理的情況下,如何確保內(nèi)存安全性? Jun 19, 2025 am 01:11 AM

Goensuresmemorysafetywithoutmanualmanagementthroughautomaticgarbagecollection,nopointerarithmetic,safeconcurrency,andruntimechecks.First,Go’sgarbagecollectorautomaticallyreclaimsunusedmemory,preventingleaksanddanglingpointers.Second,itdisallowspointe

如何在GO中創(chuàng)建緩沖頻道? (例如,make(chan int,10)) 如何在GO中創(chuàng)建緩沖頻道? (例如,make(chan int,10)) Jun 20, 2025 am 01:07 AM

在Go中創(chuàng)建緩沖通道只需在make函數(shù)中指定容量參數(shù)即可。緩沖通道允許發(fā)送操作在沒有接收者時暫存數(shù)據(jù),只要未超過指定容量,例如ch:=make(chanint,10)創(chuàng)建了一個可存儲最多10個整型值的緩沖通道;與無緩沖通道不同,發(fā)送數(shù)據(jù)時不會立即阻塞,而是將數(shù)據(jù)暫存于緩沖區(qū)中,直到被接收者取走;使用時需注意:1.容量設(shè)置應(yīng)合理以避免內(nèi)存浪費或頻繁阻塞;2.需防止緩沖區(qū)無限堆積數(shù)據(jù)導(dǎo)致內(nèi)存問題;3.可用chanstruct{}類型傳遞信號以節(jié)省資源;常見場景包括控制并發(fā)數(shù)量、生產(chǎn)者-消費者模型及異

如何使用GO進(jìn)行系統(tǒng)編程任務(wù)? 如何使用GO進(jìn)行系統(tǒng)編程任務(wù)? Jun 19, 2025 am 01:10 AM

Go是系統(tǒng)編程的理想選擇,因為它結(jié)合了C等編譯型語言的性能與現(xiàn)代語言的易用性和安全性。1.文件與目錄操作方面,Go的os包支持創(chuàng)建、刪除、重命名及檢查文件和目錄是否存在,使用os.ReadFile可一行代碼讀取整個文件,適用于編寫備份腳本或日志處理工具;2.進(jìn)程管理方面,通過os/exec包的exec.Command函數(shù)可執(zhí)行外部命令、捕獲輸出、設(shè)置環(huán)境變量、重定向輸入輸出流以及控制進(jìn)程生命周期,適合用于自動化工具和部署腳本;3.網(wǎng)絡(luò)與并發(fā)方面,net包支持TCP/UDP編程、DNS查詢及原始套

如何在GO中的結(jié)構(gòu)實例上調(diào)用方法? 如何在GO中的結(jié)構(gòu)實例上調(diào)用方法? Jun 24, 2025 pm 03:17 PM

在Go語言中,調(diào)用結(jié)構(gòu)體方法需先定義結(jié)構(gòu)體和綁定接收者的方法,使用點號訪問。定義結(jié)構(gòu)體Rectangle后,可通過值接收者或指針接收者聲明方法;1.使用值接收者如func(rRectangle)Area()int,通過rect.Area()直接調(diào)用;2.若需修改結(jié)構(gòu)體,應(yīng)使用指針接收者如func(r*Rectangle)SetWidth(...),Go會自動處理指針與值的轉(zhuǎn)換;3.嵌入結(jié)構(gòu)體時,內(nèi)嵌結(jié)構(gòu)體的方法會被提升,可直接通過外層結(jié)構(gòu)體調(diào)用;4.Go無需強制使用getter/setter,字

GO中的接口是什么?如何定義它們? GO中的接口是什么?如何定義它們? Jun 22, 2025 pm 03:41 PM

在Go語言中,接口是一種定義行為而不指定實現(xiàn)方式的類型。接口由方法簽名組成,任何實現(xiàn)這些方法的類型都自動滿足該接口。例如,定義一個Speaker接口包含Speak()方法,則所有實現(xiàn)該方法的類型均可視為Speaker。接口適用于編寫通用函數(shù)、抽象實現(xiàn)細(xì)節(jié)和測試中使用mock對象。定義接口使用interface關(guān)鍵字并列出方法簽名,無需顯式聲明類型實現(xiàn)了接口。常見用例包括日志、格式化、不同數(shù)據(jù)庫或服務(wù)的抽象,以及通知系統(tǒng)等。例如,Dog和Robot類型均可實現(xiàn)Speak方法,并傳遞給同一個Anno

如何在GO中使用字符串軟件包中的字符串函數(shù)? (例如len(),strings.contains(),strings.index(),strings.replaceall()) 如何在GO中使用字符串軟件包中的字符串函數(shù)? (例如len(),strings.contains(),strings.index(),strings.replaceall()) Jun 20, 2025 am 01:06 AM

在Go語言中,字符串操作主要通過strings包和內(nèi)置函數(shù)實現(xiàn)。1.strings.Contains()用于判斷字符串是否包含子串,返回布爾值;2.strings.Index()可查找子串首次出現(xiàn)的位置,若不存在則返回-1;3.strings.ReplaceAll()能替換所有匹配的子串,還可通過strings.Replace()控制替換次數(shù);4.len()函數(shù)用于獲取字符串字節(jié)數(shù)長度,但處理Unicode時需注意字符與字節(jié)的區(qū)別。這些功能常用于數(shù)據(jù)過濾、文本解析及字符串處理等場景。

如何使用IO軟件包在GO中使用輸入和輸出流? 如何使用IO軟件包在GO中使用輸入和輸出流? Jun 20, 2025 am 11:25 AM

TheGoiopackageprovidesinterfaceslikeReaderandWritertohandleI/Ooperationsuniformlyacrosssources.1.io.Reader'sReadmethodenablesreadingfromvarioussourcessuchasfilesorHTTPresponses.2.io.Writer'sWritemethodfacilitateswritingtodestinationslikestandardoutpu

See all articles