once
sync.Once Go'nun standart kütüphanesinde bir senkronizasyon aracıdır, eşzamanlı ortamda bir fonksiyonun sadece bir kez yürütülmesini sağlamak için kullanılır. Tembel başlatma, global kaynak başlatma vb. senaryolarda yaygın olarak kullanılır, belirli bir işlemin sadece bir kez yürütülmesini garanti eder, birden fazla goroutine eşzamanlı olarak yürütülse bile.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var once sync.Once
for i := range 10 {
wg.Add(1)
go func() {
defer wg.Done()
once.Do(func() {
fmt.Println(i)
})
}()
}
wg.Wait()
}Yukarıdaki kodda, toplam 10 goroutine vardır, ancak her ne olursa olsun, sadece bir goroutine once.Do() içinde fmt.Println(i) yürütecektir. Hangi goroutine'in yazdıracağı hangi goroutine'in once.Do()'a ilk ulaştığına bağlıdır.
Yapı
type Once struct {
done atomic.Uint32
m Mutex
}İç yapısı çok basittir:
done, yürütülüp yürütülmediğini belirtmek için kullanılan bir atomik değerm, yürütmek isteyen diğer goroutine'leri engellemek için kullanılan mutex kilidi
Prensibi yürütmeden önce kilitlemek, sonra done'u güncellemek ve yürütme tamamlandıktan sonra kilidi açmaktır.
Do
func (o *Once) Do(f func()) {
if o.done.Load() == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done.Load() == 0 {
defer o.done.Store(1)
f()
}
}Kod genel olarak çok basittir, akış şöyledir:
- Atomik değeri doğrudan yükle, eğer zaten yürütüldüyse, hemen dön
- Eğer yürütülmediyse, kilidi almaya çalış, burada birden fazla goroutine mutex için yarışıyor olabilir
- Kazanan kilidi başarıyla aldıktan sonra, hala
donedeğerini tekrar kontrol etmelidir, çünkü kilidi aldıktan sonra, başkası zaten kilit-al-yürüt-kilit-aç dizisini tamamlamış olabilir ve siz yeni uyandırıldınız. - Hedef fonksiyonu yürüt
donedeğerini güncelle- Kilidi serbest bırak
Özet
sync.Once çok öz ve verimli bir senkronizasyon aracıdır. Eşzamanlı ortamda belirli bir işlemin sadece bir kez yürütülmesini garanti eder, böylece tekrarlanan iş veya kaynak israfını önler.
