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

首頁 後端開發(fā) Golang Go Singleflight 融入您的程式碼中,而不是您的資料庫中

Go Singleflight 融入您的程式碼中,而不是您的資料庫中

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 融入您的程式碼,而不是您的資料庫(我們在這裡)

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

Go Singleflight 融入您的程式碼,而不是您的資料庫

因此,當(dāng)您同時收到多個請求相同的資料時,預(yù)設(shè)行為是每個請求都會單獨存取資料庫以獲取相同的資訊。這意味著您最終會執(zhí)行多次相同的查詢,老實說,這效率很低。

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

多個相同的請求到達資料庫

它最終會為資料庫帶來不必要的負載,這可能會減慢一切,但有一種方法可以解決這個問題。

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

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

singleflight 如何抑制重複請求

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

單程航班

Go 中的 singleflight 套件是專門為處理我們剛才討論的問題而建造的。請注意,它不是標準庫的一部分,但由 Go 團隊維護和開發(fā)。

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

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

好了,說得夠多了,讓我們深入了解 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ù)來模擬從資料庫中獲得的資料。

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

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

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

單次飛行示範

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

共享標誌確認結(jié)果已在多個 goroutine 之間重複使用。

「但是為什麼第一個 goroutine 的共享標誌為 true?我以為只有等待的 goroutine 才會共用 == true?」

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

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

「我有緩存,為什麼我需要單次飛行?」

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

在使用外部快?。ㄈ?Redis 或 Memcached)的設(shè)定中,singleflight 增加了額外的保護層,不僅為您的資料庫,也為快取本身。

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

Singleflight 與快取系統(tǒng)一起工作

此外,singleflight 有助於防止快取未命中風(fēng)暴(有時稱為「快取踩踏」)。

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

在此高峰期間,singleflight 確保這 10,000 個請求中只有一個真正到達資料庫。

但是稍後,在內(nèi)部實作部分,我們將看到 singleflight 使用全域鎖來保護正在進行的呼叫的映射,這可能會成為每個 goroutine 的單點爭用。這可能會減慢速度,尤其是在處理高並發(fā)時。

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

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

緩存未命中時的單次飛行

在此設(shè)定中,我們只在發(fā)生快取未命中時使用 singleflight。

單次航班營運

要使用 singleflight,您首先建立一個 Group 對象,它是追蹤連結(jié)到特定鍵的正在進行的函數(shù)呼叫的核心結(jié)構(gòu)。

它有兩個有助於防止重複呼叫的關(guān)鍵方法:

  • group.Do(key, func):執(zhí)行函數(shù),同時抑制重複請求。當(dāng)您呼叫 Do 時,您傳入一個鍵和一個函數(shù),如果該鍵沒有發(fā)生其他執(zhí)行,則該函數(shù)將運行。如果同一個鍵已經(jīng)有一個執(zhí)行正在進行,您的呼叫將阻塞,直到第一個執(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í)行其他操作。例如,您可以使用通道更乾淨(jìng)地處理逾時或取消:

package singleflight

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

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

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

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

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

讓我們更新範例以使用 Forget() 並查看該函數(shù)實際被呼叫了多少次:

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”)呼叫 Do(),它們的請求合併為一次執(zhí)行,結(jié)果在兩個 Goroutine 之間共用。

Goroutine 2,另一方面,在執(zhí)行 Do() 之前呼叫 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的整個實作只有大約150行程式碼。

基本上,每個唯一的鍵都有一個管理其執(zhí)行的結(jié)構(gòu)。如果 goroutine 呼叫 Do() 並發(fā)現(xiàn) key 已經(jīng)存在,則該呼叫將被阻塞,直到第一次執(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):此互斥鎖保護整個鍵映射,而不是每個鍵一個鎖,它確保新增或刪除鍵是執(zhí)行緒安全的。
  • WaitGroup (g.call.wg):WaitGroup 用於等待與特定鍵關(guān)聯(lián)的第一個 goroutine 完成其工作。

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

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

「這對效能不是很不利嗎?」

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

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

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

如果找到該鍵(另一個 goroutine 已經(jīng)在運行該任務(wù)),我們只需增加一個計數(shù)器(c.dups)來追蹤重複請求,而不是開始新的呼叫。然後,goroutine 釋放鎖定並透過在關(guān)聯(lián)的 WaitGroup 上呼叫 call.wg.Wait() 來等待原始任務(wù)完成。

當(dāng)原始任務(wù)完成時,這個 goroutine 會取得結(jié)果並避免再次執(zhí)行該任務(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 負責(zé)執(zhí)行該任務(wù)。

此時,我們建立一個新的呼叫對象,將其新增至映射中,並初始化其 WaitGroup。然後,我們解鎖互斥體並繼續(xù)透過輔助方法 g.doCall(c, key, fn) 自行執(zhí)行任務(wù)。當(dāng)任務(wù)完成時,任何等待的 goroutine 都會被 wg.Wait() 呼叫解除阻塞。

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

  • 如果函數(shù)發(fā)生恐慌,我們會捕捉它,將其包裝在一個恐慌錯誤中,然後引發(fā)恐慌。
  • 如果函數(shù)回傳 errGoexit,我們呼叫 runtime.Goexit() 來正確退出 goroutine。
  • 如果這只是一個正常錯誤,我們會在呼叫時設(shè)定該錯誤。

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

「等等,什麼是runtime.Goexit()?」

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

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

  • 它不會引發(fā)恐慌,所以你無法用recover()捕捉它。
  • 只有呼叫 Goexit() 的 goroutine 被終止,所有其他 goroutine 都保持正常運作。

現(xiàn)在,這是一個有趣的怪癖(與我們的主題沒有直接關(guān)係,但值得一提)。如果你在主協(xié)程中呼叫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()僅終止目前的goroutine並且無法被recover()捕獲,我們?nèi)绾蝹蓽y它是否被呼叫?

關(guān)鍵在於,當(dāng)呼叫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
}

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

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

package singleflight

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

這段程式碼不是直接在recover區(qū)塊內(nèi)設(shè)定recovered = true,而是透過在recover()區(qū)塊之後將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ù)時,但在呼叫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ù)呼叫了runtime.Goexit(),則不需要採取任何進一步的操作,因為goroutine已經(jīng)處於關(guān)閉過程中,我們只是讓它發(fā)生而不干擾。

差不多就是這樣,除了我們討論過的特殊情況之外,沒有什麼太複雜的。

保持聯(lián)繫

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

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

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

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

我們是誰

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

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

以上是Go Singleflight 融入您的程式碼中,而不是您的資料庫中的詳細內(nèi)容。更多資訊請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願投稿,版權(quán)歸原作者所有。本站不承擔(dān)相應(yīng)的法律責(zé)任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請聯(lián)絡(luò)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)

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

Go默認將程序編譯為獨立二進製文件,主要原因是靜態(tài)鏈接。 1.部署更簡單:無需額外安裝依賴庫,可直接跨Linux發(fā)行版運行;2.二進制體積更大:包含所有依賴導(dǎo)致文件尺寸增加,但可通過構(gòu)建標誌或壓縮工具優(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進行系統(tǒng)編程任務(wù)? 如何使用GO進行系統(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.進程管理方面,通過os/exec包的exec.Command函數(shù)可執(zhí)行外部命令、捕獲輸出、設(shè)置環(huá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)細節(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