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

Rumah pembangunan bahagian belakang Golang Go Singleflight Meleleh dalam Kod Anda, Bukan dalam?DB Anda

Go Singleflight Meleleh dalam Kod Anda, Bukan dalam?DB Anda

Nov 05, 2024 pm 12:27 PM

Artikel asal disiarkan di blog VictoriaMetrics: https://victoriametrics.com/blog/go-singleflight/

Siaran ini adalah sebahagian daripada siri tentang pengendalian concurrency dalam Go:

  • Pergi sync.Mutex: Mod Normal dan Kebuluran
  • Pergi sync.WaitGroup dan Masalah Penjajaran
  • Segerakkan. Kolam dan Mekanik Di Belakangnya
  • Pergi sync.Cond, Mekanisme Penyegerakan Yang Paling Diabaikan
  • Pergi sync.Map: Alat yang Tepat untuk Kerja yang Tepat
  • Go Sync.Once is Simple... Betulkah?
  • Pergi Singleflight Lebur dalam Kod Anda, Bukan dalam DB Anda (Kami di sini)

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

Pergi Singleflight Lebur dalam Kod Anda, Bukan dalam DB Anda

Jadi, apabila anda mendapat berbilang permintaan yang masuk pada masa yang sama meminta data yang sama, tingkah laku lalai ialah setiap permintaan tersebut akan pergi ke pangkalan data secara individu untuk mendapatkan maklumat yang sama . Maksudnya ialah anda akan melaksanakan pertanyaan yang sama beberapa kali, yang, sejujurnya, adalah tidak cekap.

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

Berbilang permintaan yang sama memukul pangkalan data

Ia akhirnya meletakkan beban yang tidak perlu pada pangkalan data anda, yang boleh melambatkan segala-galanya, tetapi ada cara untuk mengatasinya.

Ideanya ialah hanya permintaan pertama yang benar-benar pergi ke pangkalan data. Permintaan selebihnya menunggu untuk yang pertama selesai. Setelah data kembali daripada permintaan awal, yang lain hanya mendapat hasil yang sama—tiada pertanyaan tambahan diperlukan.

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

Cara penerbangan tunggal menyekat permintaan pendua

Jadi, sekarang anda sudah mendapat idea yang bagus tentang siaran ini, bukan?

penerbangan tunggal

Pakej penerbangan tunggal dalam Go dibina khusus untuk mengendalikan perkara yang baru sahaja kita bincangkan. Dan sekadar makluman, ia bukan sebahagian daripada pustaka standard tetapi ia diselenggara dan dibangunkan oleh pasukan Go.

Apa yang dilakukan oleh singleflight ialah memastikan bahawa hanya satu daripada gorout tersebut yang benar-benar menjalankan operasi, seperti mendapatkan data daripada pangkalan data. Ia membenarkan hanya satu operasi "dalam penerbangan" (berterusan) untuk sekeping data yang sama (dikenali sebagai "kunci") pada bila-bila masa.

Jadi, jika gorout lain meminta data yang sama (kunci yang sama) semasa operasi itu masih berjalan, mereka hanya akan menunggu. Kemudian, apabila yang pertama selesai, semua yang lain mendapat hasil yang sama tanpa perlu menjalankan operasi sekali lagi.

Baiklah, cukup bercakap, mari kita selami demo pantas untuk melihat cara penerbangan tunggal berfungsi dalam tindakan:

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

Apa yang berlaku di sini:

Kami sedang mensimulasikan situasi di mana 5 gorout cuba mengambil data yang sama hampir pada masa yang sama, dengan jarak 60ms. Untuk memastikannya mudah, kami menggunakan nombor rawak untuk meniru data yang diambil daripada pangkalan data.

Dengan singleflight.Group, kami memastikan hanya goroutine pertama benar-benar menjalankan fetchData() dan yang lain menunggu hasilnya.

Barisan v, err, shared := g.Do("key-fetch-data", fetchData) memberikan kunci unik ("key-fetch-data") untuk menjejaki permintaan ini. Jadi, jika goroutine lain meminta kunci yang sama semasa yang pertama masih mengambil data, ia menunggu keputusan daripada memulakan panggilan baharu.

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

Demonstrasi penerbangan tunggal dalam aksi

Setelah panggilan pertama selesai, mana-mana goroutine menunggu mendapat hasil yang sama, seperti yang dapat kita lihat dalam output. Walaupun kami mempunyai 5 goroutine yang meminta data, fetchData hanya dijalankan dua kali, yang merupakan rangsangan besar.

Bendera kongsi mengesahkan bahawa hasilnya telah digunakan semula merentasi berbilang goroutin.

"Tetapi mengapa bendera yang dikongsi benar untuk goroutine pertama? Saya fikir hanya yang menunggu sahaja yang akan berkongsi == benar?"

Ya, ini mungkin terasa agak berlawanan dengan intuisi jika anda berfikir hanya goroutine menunggu yang sepatutnya dikongsi == benar.

Perkaranya, pembolehubah yang dikongsi dalam g.Do memberitahu anda sama ada hasilnya dikongsi antara berbilang pemanggil. Ia pada asasnya mengatakan, "Hei, hasil carian ini digunakan oleh lebih daripada seorang pemanggil." Ini bukan tentang siapa yang menjalankan fungsi itu, ia hanya isyarat bahawa hasilnya digunakan semula merentasi berbilang goroutine.

"Saya mempunyai cache, mengapa saya memerlukan penerbangan tunggal?"

Jawapan ringkasnya ialah: cache dan penerbangan tunggal menyelesaikan masalah yang berbeza, dan ia sebenarnya berfungsi dengan sangat baik bersama-sama.

Dalam persediaan dengan cache luaran (seperti Redis atau Memcached), singleflight menambah lapisan perlindungan tambahan, bukan sahaja untuk pangkalan data anda tetapi juga untuk cache itu sendiri.

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

Penerbangan tunggal berfungsi bersama sistem cache

Selain itu, penerbangan tunggal membantu melindungi daripada cache miss storm (kadangkala dipanggil "cache rempuhan").

Biasanya, apabila permintaan meminta data, jika data berada dalam cache, bagus - ia adalah cache hit. Jika data tiada dalam cache, ia adalah satu kehilangan cache. Katakan 10,000 permintaan melanda sistem sekaligus sebelum cache dibina semula, pangkalan data tiba-tiba boleh diselar dengan 10,000 pertanyaan yang sama pada masa yang sama.

Semasa puncak ini, penerbangan tunggal memastikan bahawa hanya satu daripada 10,000 permintaan itu benar-benar mencapai pangkalan data.

Tetapi kemudian, dalam bahagian pelaksanaan dalaman, kita akan melihat bahawa penerbangan tunggal menggunakan kunci global untuk melindungi peta panggilan dalam penerbangan, yang boleh menjadi satu titik perbalahan bagi setiap goroutine. Ini boleh melambatkan keadaan, terutamanya jika anda berhadapan dengan kesesuaian yang tinggi.

Model di bawah mungkin berfungsi lebih baik untuk mesin dengan berbilang CPU:

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

Penerbangan tunggal pada cache terlepas

Dalam persediaan ini, kami hanya menggunakan penerbangan tunggal apabila kehilangan cache berlaku.

Operasi Singleflight

Untuk menggunakan penerbangan tunggal, anda mula-mula membuat objek Kumpulan, yang merupakan struktur teras yang menjejaki panggilan fungsi yang sedang dijalankan yang dipautkan kepada kekunci tertentu.

Ia mempunyai dua kaedah utama yang membantu menghalang panggilan pendua:

  • group.Do(key, func): Menjalankan fungsi anda sambil menyekat permintaan pendua. Apabila anda memanggil Do, anda memasukkan kunci dan fungsi, jika tiada pelaksanaan lain berlaku untuk kekunci itu, fungsi itu berjalan. Jika sudah ada pelaksanaan yang sedang dijalankan untuk kunci yang sama, panggilan anda disekat sehingga yang pertama selesai dan mengembalikan hasil yang sama.
  • group.DoChan(key, func): Sama seperti group.Do, tetapi bukannya menyekat, ia memberi anda saluran (<-chan Result). Anda akan menerima keputusan sebaik sahaja ia siap, menjadikannya berguna jika anda lebih suka mengendalikan hasil secara tidak segerak atau jika anda memilih melalui berbilang saluran.

Kami telah melihat cara menggunakan g.Do() dalam demo, mari lihat cara menggunakan g.DoChan() dengan fungsi pembalut yang diubah suai:

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
}

Sejujurnya, menggunakan DoChan() di sini tidak banyak berubah berbanding dengan Do(), kerana kami masih menunggu keputusan dengan operasi penerimaan saluran (<-ch), yang pada dasarnya menyekat perkara yang sama cara.

Di mana DoChan() bersinar ialah apabila anda ingin memulakan operasi dan melakukan perkara lain tanpa menyekat goroutine. Contohnya, anda boleh mengendalikan tamat masa atau pembatalan dengan lebih bersih menggunakan saluran:

package singleflight

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

Contoh ini turut memaparkan beberapa isu yang mungkin anda hadapi dalam senario dunia sebenar:

  • Groutine pertama mungkin mengambil masa yang lebih lama daripada yang dijangkakan disebabkan oleh perkara seperti respons rangkaian yang perlahan, pangkalan data tidak responsif, dsb. Dalam kes ini, semua goroutin menunggu yang lain tersekat lebih lama daripada yang anda mahukan. Tamat masa boleh membantu di sini, tetapi sebarang permintaan baharu masih menunggu di belakang yang pertama.
  • Data yang anda ambil mungkin kerap berubah, jadi apabila permintaan pertama selesai, hasilnya mungkin sudah lapuk. Ini bermakna kita memerlukan cara untuk membatalkan kunci dan mencetuskan pelaksanaan baharu.

Ya, penerbangan tunggal menyediakan cara untuk mengendalikan situasi seperti ini dengan kumpulan.Kaedah Lupakan(kunci), yang membolehkan anda membuang pelaksanaan yang sedang berjalan.

Kaedah Forget() mengalih keluar kunci daripada peta dalaman yang menjejaki panggilan fungsi yang sedang berjalan. Ia seperti "membatalkan" kunci, jadi jika anda memanggil g.Do() sekali lagi dengan kunci itu, ia akan melaksanakan fungsi itu seolah-olah ia adalah permintaan baharu, dan bukannya menunggu pelaksanaan sebelumnya untuk selesai.

Mari kemas kini contoh kami untuk menggunakan Forget() dan lihat berapa kali fungsi itu sebenarnya dipanggil:

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 dan Goroutine 1 kedua-duanya memanggil Do() dengan kunci yang sama ("key-fetch-data"), dan permintaan mereka digabungkan menjadi satu pelaksanaan dan hasilnya dikongsi antara dua goroutine.

Goroutine 2, sebaliknya, memanggil Forget() sebelum menjalankan Do(). Ini mengosongkan sebarang hasil sebelumnya yang terikat dengan "data pengambilan kunci", jadi ia mencetuskan pelaksanaan baharu fungsi tersebut.

Ringkasnya, walaupun penerbangan tunggal berguna, ia masih boleh mempunyai beberapa kes tepi, contohnya:

  • Jika goroutine pertama disekat terlalu lama, semua yang lain menunggunya juga akan tersekat. Dalam kes sedemikian, menggunakan konteks tamat masa atau pernyataan pilihan dengan tamat masa boleh menjadi pilihan yang lebih baik.
  • Jika permintaan pertama mengembalikan ralat atau panik, ralat atau panik yang sama itu akan merebak ke semua goroutine lain yang menunggu hasilnya.

Jika anda perasan semua isu yang telah kami bincangkan, mari selami bahagian seterusnya untuk membincangkan cara penerbangan tunggal sebenarnya berfungsi di bawah hud.

Cara Singleflight Berfungsi

Daripada menggunakan penerbangan tunggal, anda mungkin sudah mempunyai idea asas tentang cara ia berfungsi secara dalaman, keseluruhan pelaksanaan penerbangan tunggal hanyalah kira-kira 150 baris kod.

Pada asasnya, setiap kunci unik mendapat struct yang menguruskan pelaksanaannya. Jika goroutine memanggil Do() dan mendapati bahawa kunci sudah wujud, panggilan itu akan disekat sehingga pelaksanaan pertama selesai, dan berikut ialah strukturnya:

// 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
}

Dua primitif penyegerakan digunakan di sini:

  • Mutex kumpulan (g.mu): Mutex ini melindungi seluruh peta kunci, bukan satu kunci setiap kekunci, ia memastikan menambah atau mengalih keluar kunci selamat untuk benang.
  • WaitGroup (g.call.wg): WaitGroup digunakan untuk menunggu goroutine pertama yang dikaitkan dengan kunci tertentu untuk menyelesaikan kerjanya.

Kami akan menumpukan pada kaedah kumpulan.Do() di sini kerana kaedah lain, kumpulan.DoChan(), berfungsi dengan cara yang sama. Kaedah group.Forget() juga mudah kerana ia hanya mengalih keluar kunci daripada peta.

Apabila anda memanggil kumpulan.Do(), perkara pertama yang dilakukan ialah mengunci seluruh peta panggilan (g.mu).

"Bukankah itu buruk untuk persembahan?"

Ya, ini mungkin tidak sesuai untuk prestasi dalam setiap kes (sentiasa baik untuk menanda aras dahulu) kerana penerbangan tunggal mengunci keseluruhan kekunci. Jika anda menyasarkan prestasi yang lebih baik atau bekerja pada skala yang tinggi, pendekatan yang baik adalah untuk memecahkan atau mengedarkan kunci. Daripada menggunakan hanya satu kumpulan penerbangan tunggal, anda boleh menyebarkan beban ke beberapa kumpulan, seperti melakukan "multiflight" sebaliknya

Untuk rujukan, lihat repo ini: shardedsingleflight.

Sekarang, setelah ia mempunyai kunci, kumpulan itu melihat peta dalaman (g.m), jika sudah ada panggilan yang sedang atau selesai untuk kunci yang diberikan. Peta ini menjejaki sebarang kerja yang sedang berjalan atau selesai, dengan kunci memetakan kepada tugasan yang sepadan.

Jika kunci ditemui (goroutine lain sudah menjalankan tugas), bukannya memulakan panggilan baharu, kami hanya menambah pembilang (c.dups) untuk menjejaki permintaan pendua. Goroutine kemudiannya melepaskan kunci dan menunggu tugas asal selesai dengan memanggil call.wg.Wait() pada WaitGroup yang berkaitan.

Apabila tugasan asal selesai, goroutine ini mengambil keputusan dan mengelak daripada menjalankan tugas itu semula.

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

Jika tiada goroutine lain yang berfungsi pada kunci itu, goroutine semasa bertanggungjawab untuk melaksanakan tugas itu.

Pada ketika ini, kami mencipta objek panggilan baharu, menambahkannya pada peta dan memulakan WaitGroupnya. Kemudian, kami membuka kunci mutex dan meneruskan untuk melaksanakan tugas itu sendiri melalui kaedah pembantu g.doCall(c, key, fn). Apabila tugas selesai, mana-mana gorouti menunggu dinyahsekat oleh panggilan wg.Wait().

Tiada yang terlalu liar di sini, kecuali cara kami menangani ralat, terdapat tiga senario yang mungkin:

  • Jika fungsi panik, kami menangkapnya, membungkusnya dengan panikRalat, dan membuang panik.
  • Jika fungsi mengembalikan errGoexit, kami memanggil runtime.Goexit() untuk keluar dari goroutine dengan betul.
  • Jika ia hanya ralat biasa, kami menetapkan ralat itu pada panggilan.

Di sinilah perkara mula menjadi lebih bijak dalam kaedah penolong g.doCall().

"Tunggu, apakah masa jalanan.Goexit()?"

Sebelum kita menyelami kod, izinkan saya menerangkan dengan cepat, runtime.Goexit() digunakan untuk menghentikan pelaksanaan goroutine.

Apabila goroutine memanggil Goexit(), ia berhenti dan mana-mana fungsi tertunda masih dijalankan dalam tertib Masuk Pertama Keluar (LIFO), seperti biasa. Ia serupa dengan panik, tetapi terdapat beberapa perbezaan:

  • Ia tidak mencetuskan panik, jadi anda tidak boleh menangkapnya dengan recover().
  • Hanya goroutine yang memanggil Goexit() akan ditamatkan dan semua goroutine lain terus berjalan dengan baik.

Sekarang, inilah ciri menarik (tidak berkaitan secara langsung dengan topik kami, tetapi patut disebut). Jika anda memanggil runtime.Goexit() dalam goroutine utama (seperti dalam main()), lihat ini:

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

Apa yang berlaku ialah Goexit() menamatkan goroutine utama, tetapi jika terdapat goroutine lain yang masih berjalan, program akan diteruskan kerana masa jalanan Go kekal hidup selagi sekurang-kurangnya satu goroutine aktif. Walau bagaimanapun, apabila tiada gorouti yang tinggal, ia ranap dengan ralat "tiada gorouti", sejenis sarung sudut kecil yang menyeronokkan.

Sekarang, kembali kepada kod kami, jika runtime.Goexit() hanya menamatkan goroutine semasa dan tidak boleh ditangkap oleh recover(), bagaimanakah kami dapat mengesan jika ia telah dipanggil?

Kuncinya terletak pada fakta bahawa apabila runtime.Goexit() digunakan, sebarang kod selepasnya tidak dapat dilaksanakan.

// 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
}

Dalam kes di atas, baris normalReturn = true tidak pernah dilaksanakan selepas memanggil runtime.Goexit(). Jadi, di dalam penangguhan, kita boleh menyemak sama ada normalReturn masih palsu untuk mengesan kaedah khas itu dipanggil.

Langkah seterusnya ialah memikirkan sama ada tugas itu panik atau tidak. Untuk itu, kami menggunakan recover() sebagai pulangan biasa, walaupun kod sebenar dalam penerbangan tunggal adalah lebih halus sedikit:

package singleflight

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

Daripada tetapan dipulihkan = benar terus di dalam blok pulih, kod ini menjadi sedikit mewah dengan menetapkan dipulihkan selepas blok recover() sebagai baris terakhir.

Jadi, mengapa ini berkesan?

Apabila runtime.Goexit() dipanggil, ia menamatkan keseluruhan goroutine, sama seperti panik(). Walau bagaimanapun, jika panic() dipulihkan, hanya rantaian fungsi antara panic() dan recover() ditamatkan, bukan keseluruhan goroutine.

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

Pengendalian panik dan masa jalan.Goexit() dalam penerbangan tunggal

Itulah sebabnya recovered = true ditetapkan di luar penangguhan yang mengandungi recover(), ia hanya dilaksanakan dalam dua kes: apabila fungsi selesai seperti biasa atau apabila panik dipulihkan, tetapi bukan apabila runtime.Goexit() dipanggil.

Melangkah ke hadapan, kami akan membincangkan cara setiap kes dikendalikan.

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
}

Jika tugasan panik semasa pelaksanaan, panik itu ditangkap dan disimpan dalam c.err sebagai panicError, yang memegang kedua-dua nilai panik dan surih tindanan. singleflight menangkap panik untuk membersihkan dengan anggun, tetapi ia tidak menelannya, ia menimbulkan semula panik selepas mengendalikan keadaannya.

Ini bermakna panik akan berlaku dalam goroutine yang melaksanakan tugas (yang pertama memulakan operasi), dan semua goroutine lain yang menunggu keputusan juga akan panik.

Memandangkan panik ini berlaku dalam kod pembangun, terpulang kepada kami untuk menanganinya dengan betul.

Kini, masih terdapat satu kes khas yang perlu kita pertimbangkan: apabila gorout lain menggunakan kaedah kumpulan.DoChan() dan menunggu keputusan melalui saluran. Dalam kes ini, penerbangan tunggal tidak boleh panik dalam gorout tersebut. Sebaliknya, ia melakukan apa yang dipanggil panik tidak boleh dipulihkan (go panic(e)), yang menyebabkan aplikasi kami ranap.

Akhir sekali, jika tugas itu dipanggil runtime.Goexit(), anda tidak perlu mengambil apa-apa tindakan selanjutnya kerana goroutine sudah dalam proses untuk dimatikan, dan kami hanya membiarkan perkara itu berlaku tanpa campur tangan.

Dan itu sahaja, tiada yang terlalu rumit kecuali untuk kes-kes khas yang telah kita bincangkan.

Kekal Terhubung

Hai, saya Phuong Le, seorang jurutera perisian di VictoriaMetrics. Gaya penulisan di atas memfokuskan pada kejelasan dan kesederhanaan, menerangkan konsep dengan cara yang mudah difahami, walaupun ia tidak sentiasa sejajar dengan ketepatan akademik.

Jika anda melihat apa-apa yang sudah lapuk atau jika anda mempunyai soalan, jangan teragak-agak untuk menghubungi. Anda boleh menghantar DM kepada saya di X(@func25).

Beberapa siaran lain yang mungkin anda minati:

  • Pembaca Go I/O, Penulis dan Data Bergerak.
  • How Go Arrays Berfungsi dan Dapatkan Tricky dengan Untuk-Julat
  • Slices in Go: Grow Big or Go Home
  • Go Maps Diterangkan: Bagaimana Pasangan Nilai Kunci Sebenarnya Disimpan
  • Golang Defer: Dari Asas Kepada Perangkap
  • Menjual atau pergi vendor mod: Apakah itu?

Siapa Kita

Jika anda ingin memantau perkhidmatan anda, menjejaki metrik dan melihat prestasi semuanya, anda mungkin ingin menyemak VictoriaMetrics. Ia merupakan cara yang pantas, sumber terbuka dan penjimatan kos untuk mengawasi infrastruktur anda.

Dan kami adalah Gophers, peminat yang suka menyelidik, mencuba dan berkongsi pengetahuan tentang Go dan ekosistemnya.

Atas ialah kandungan terperinci Go Singleflight Meleleh dalam Kod Anda, Bukan dalam?DB Anda. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Apakah implikasi pautan statik Go secara lalai? Apakah implikasi pautan statik Go secara lalai? Jun 19, 2025 am 01:08 AM

Pergi menyusun program ke dalam binari mandiri secara lalai, sebab utama adalah menghubungkan statik. 1. Penyebaran yang lebih mudah: Tiada pemasangan tambahan perpustakaan ketergantungan, boleh dijalankan secara langsung di seluruh pengagihan Linux; 2. Saiz binari yang lebih besar: termasuk semua kebergantungan menyebabkan saiz fail meningkat, tetapi boleh dioptimumkan melalui bendera bangunan atau alat pemampatan; 3. Predikabiliti dan keselamatan yang lebih tinggi: Elakkan risiko yang dibawa oleh perubahan dalam versi perpustakaan luaran dan meningkatkan kestabilan; 4. Fleksibiliti operasi terhad: Tidak boleh kemas kini panas perpustakaan yang dikongsi, dan penyusunan semula dan penggunaan diperlukan untuk memperbaiki kelemahan ketergantungan. Ciri-ciri ini sesuai untuk alat CLI, microservices dan senario lain, tetapi perdagangan diperlukan dalam persekitaran di mana penyimpanan dihadkan atau bergantung kepada pengurusan berpusat.

Bagaimanakah pergi memastikan keselamatan memori tanpa pengurusan memori manual seperti di C? Bagaimanakah pergi memastikan keselamatan memori tanpa pengurusan memori manual seperti di C? Jun 19, 2025 am 01:11 AM

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

Bagaimana saya membuat saluran buffered di Go? (mis., Buat (Chan int, 10)) Bagaimana saya membuat saluran buffered di Go? (mis., Buat (Chan int, 10)) Jun 20, 2025 am 01:07 AM

Untuk membuat saluran penampan di Go, hanya tentukan parameter kapasiti dalam fungsi membuat. Saluran Buffer membolehkan operasi penghantaran untuk menyimpan data sementara apabila tiada penerima, selagi kapasiti yang ditentukan tidak melebihi. Sebagai contoh, Ch: = Make (Chanint, 10) mencipta saluran penampan yang boleh menyimpan sehingga 10 nilai integer; Tidak seperti saluran yang tidak dibuang, data tidak akan disekat dengan segera apabila menghantar, tetapi data akan disimpan sementara dalam penampan sehingga ia diambil oleh penerima; Apabila menggunakannya, sila ambil perhatian: 1. Tetapan kapasiti harus munasabah untuk mengelakkan sisa memori atau penyekatan kerap; 2. Penimbal perlu mencegah masalah ingatan daripada terkumpul selama -lamanya dalam penampan; 3. Isyarat boleh diluluskan oleh jenis Chanstruct {} untuk menjimatkan sumber; Senario biasa termasuk mengawal bilangan konkurensi, model pengguna dan pembezaan

Bagaimanakah anda boleh menggunakan tugas pengaturcaraan sistem? Bagaimanakah anda boleh menggunakan tugas pengaturcaraan sistem? Jun 19, 2025 am 01:10 AM

GO sangat sesuai untuk pengaturcaraan sistem kerana ia menggabungkan prestasi bahasa yang disusun seperti C dengan kemudahan penggunaan dan keselamatan bahasa moden. 1. Dari segi operasi fail dan direktori, pakej OS Go menyokong penciptaan, penghapusan, penamaan semula dan memeriksa sama ada fail dan direktori wujud. Gunakan OS.READFILE untuk membaca keseluruhan fail dalam satu baris kod, yang sesuai untuk menulis skrip sandaran atau alat pemprosesan log; 2. Dari segi pengurusan proses, fungsi exec.command pakej OS/EXEC boleh melaksanakan arahan luaran, menangkap output, menetapkan pembolehubah persekitaran, aliran input dan output mengalihkan, dan kitaran hayat proses kawalan, yang sesuai untuk alat automasi dan skrip penempatan; 3. Dari segi rangkaian dan kesesuaian, pakej bersih menyokong pengaturcaraan TCP/UDP, pertanyaan DNS dan set asal.

Bagaimanakah saya memanggil kaedah pada contoh struct di GO? Bagaimanakah saya memanggil kaedah pada contoh struct di GO? Jun 24, 2025 pm 03:17 PM

Dalam bahasa Go, memanggil kaedah struktur memerlukan terlebih dahulu menentukan struktur dan kaedah yang mengikat penerima, dan mengaksesnya menggunakan nombor titik. Selepas menentukan segi empat tepat struktur, kaedah boleh diisytiharkan melalui penerima nilai atau penerima penunjuk; 1. Gunakan penerima nilai seperti kawasan func (rrectangle) int dan terus memanggilnya melalui rect.area (); 2. Jika anda perlu mengubah suai struktur, gunakan penerima penunjuk seperti func (R*segi empat) setWidth (...), dan GO akan secara automatik mengendalikan penukaran penunjuk dan nilai; 3. Apabila membenamkan struktur, kaedah struktur tertanam akan diperbaiki, dan ia boleh dipanggil secara langsung melalui struktur luar; 4. Pergi tidak perlu memaksa menggunakan getter/setter,

Apakah antaramuka dalam GO, dan bagaimana saya menentukannya? Apakah antaramuka dalam GO, dan bagaimana saya menentukannya? Jun 22, 2025 pm 03:41 PM

Di GO, antara muka adalah jenis yang mentakrifkan tingkah laku tanpa menentukan pelaksanaan. Antara muka terdiri daripada tandatangan kaedah, dan mana -mana jenis yang melaksanakan kaedah ini secara automatik memenuhi antara muka. Sebagai contoh, jika anda menentukan antara muka penceramah yang mengandungi kaedah bercakap (), semua jenis yang melaksanakan kaedah boleh dipertimbangkan pembesar suara. Antara muka sesuai untuk menulis fungsi umum, butiran pelaksanaan abstrak, dan menggunakan objek mengejek dalam ujian. Menentukan antara muka menggunakan kata kunci antara muka dan menyenaraikan tandatangan kaedah, tanpa secara jelas mengisytiharkan jenis untuk melaksanakan antara muka. Kes penggunaan biasa termasuk log, pemformatan, abstraksi pangkalan data atau perkhidmatan yang berbeza, dan sistem pemberitahuan. Sebagai contoh, kedua -dua jenis anjing dan robot boleh melaksanakan kaedah bercakap dan menyampaikannya kepada anno yang sama

Bagaimana saya menggunakan fungsi rentetan dari pakej Strings di GO? (mis., len (), strings.contains (), strings.index (), strings.replaceall ()) Bagaimana saya menggunakan fungsi rentetan dari pakej Strings di GO? (mis., len (), strings.contains (), strings.index (), strings.replaceall ()) Jun 20, 2025 am 01:06 AM

Dalam bahasa Go, operasi rentetan terutamanya dilaksanakan melalui pakej rentetan dan fungsi terbina dalam. 1.Strings.Contains () digunakan untuk menentukan sama ada rentetan mengandungi substring dan mengembalikan nilai boolean; 2.Strings.index () boleh mencari lokasi di mana substring muncul untuk kali pertama, dan jika ia tidak wujud, ia kembali -1; 3.Strings.ReplaceAll () boleh menggantikan semua substrings yang sepadan, dan juga boleh mengawal bilangan pengganti melalui string.replace (); 4. Len () Fungsi digunakan untuk mendapatkan panjang bait rentetan, tetapi apabila memproses Unicode, anda perlu memberi perhatian kepada perbezaan antara aksara dan bait. Fungsi ini sering digunakan dalam senario seperti penapisan data, parsing teks, dan pemprosesan rentetan.

Bagaimana saya menggunakan pakej IO untuk berfungsi dengan aliran input dan output di GO? Bagaimana saya menggunakan pakej IO untuk berfungsi dengan aliran input dan output di GO? Jun 20, 2025 am 11:25 AM

TheGoioPackageProvidesInderFacesLikeReaderAndWritertohandlei/ooperatiationUniformlyAsssources.1.io.Reader'sReadmethodenablesreadingingfromvarioussourcessuchasfilesorhtpresponses.2.WriterSwriteShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileShacileS.

See all articles