cond
sync.Cond Go'nun standart kütüphane koşul değişkenidir ve manuel başlatma gerektiren tek senkronizasyon aracıdır. Diğer senkronizasyon ilkelilerinden farklı olarak, sync.Cond paylaşılan kaynaklara erişimi korumak için bir mutex kilidi (sync.Mutex) geçirilmesini gerektirir. Goroutine'lerin belirli bir koşul sağlanana kadar bekleme durumuna girmesine ve koşul sağlandığında uyandırılmasına olanak tanır.
Örnek Kod
package main
import (
"fmt"
"sync"
"time"
)
var i = 0
func main() {
var mu sync.Mutex
var wg sync.WaitGroup
// Create a condition variable, passing in the mutex
cd := sync.NewCond(&mu)
// Add 4 goroutines to be processed
wg.Add(4)
// Create 3 goroutines, each waiting for the condition to be met
for j := range 3 {
go func() {
defer wg.Done()
mu.Lock()
for i <= 100 {
// When condition is not met, goroutine blocks here
cd.Wait()
}
fmt.Printf("%d wake up\n", j)
mu.Unlock()
}()
}
// Create a goroutine to update the condition and wake up other goroutines
go func() {
defer wg.Done()
for {
mu.Lock()
i++ // Update shared variable
mu.Unlock()
if i > 100 {
cd.Broadcast() // Wake up all waiting goroutines when condition is met
break
}
time.Sleep(time.Millisecond * 10) // Simulate workload
}
}()
// Wait for all goroutines to complete
wg.Wait()
}Yukarıdaki örnekte, paylaşılan değişken i çoklu goroutine'ler tarafından eşzamanlı olarak erişilir ve değiştirilir. Mutex kilidi mu aracılığıyla, i'ye erişimin eşzamanlı koşullar altında güvenli olduğu sağlanır. Sonra, sync.NewCond(&mu) aracılığıyla, bir koşul değişkeni cd oluşturulur, bu beklerken paylaşılan kaynaklara erişimin senkronize edilmesini sağlamak için mu kilidine bağlıdır.
- Üç bekleyen goroutine: Her goroutine
cd.Wait()aracılığıyla kendini engeller, koşul sağlanana kadar (i > 100). Bu goroutine'ler paylaşılan kaynaki'nin değeri güncellenene kadar engellenmiş kalır. - Koşulu güncelleyen ve diğerlerini uyandıran bir goroutine: Koşul sağlandığında (yani
i > 100), bu goroutinecd.Broadcast()aracılığıyla tüm bekleyen goroutine'leri uyandırır, onların yürütmeye devam etmesini sağlar.
Yapı
type Cond struct {
// L is held while observing or changing the condition
L Locker
notify notifyList
}
type notifyList struct {
// wait is the ticket number of the next waiter. It is atomically
// incremented outside the lock.
wait atomic.Uint32
notify uint32
// List of parked waiters.
lock mutex
head *sudog
tail *sudog
}Yapısı karmaşık değildir:
L, mutex kilidi, burada tipLockerarayüzüdür, somut bir kilit tipi değilnotify, bekleyen goroutine'ler için bildirim bağlı listesi
Daha önemli kısım runtime.notifyList yapısıdır:
wait, atomik değer, kaç tane bekleyen goroutine olduğunu kaydedernotify, uyandırılacak bir sonraki goroutine'e işaret eder, 0'dan başlar ve artarlock, mutex kilidi, geçirdiğimiz kilit değil,runtimetarafından dahili olarak uygulanan bir kilithead,tail, bağlı liste işaretçileri
Sadece üç metodu vardır:
Wait, engelle ve bekleSignal, bir bekleyen goroutine'i uyandırBroadcast, tüm bekleyen goroutine'leri uyandır
Uygulamasının çoğu runtime kütüphanesi altında gizlenmiştir. Bu uygulamalar runtime/sema.go dosyasında bulunur, bu yüzden standart kütüphanedeki kodu çok kısadır. Temel prensibi kilitli engelleme kuyruğudur.
Wait
Wait metodu goroutine'in kendisinin uyanana kadar engelleme beklemeye girmesine neden olur.
func (c *Cond) Wait() {
t := runtime_notifyListAdd(&c.notify)
c.L.Unlock()
runtime_notifyListWait(&c.notify, t)
c.L.Lock()
}İlk olarak kendini notifyList'e ekler, ancak aslında sadece notifyList.wait'i bir artırır. Buradaki işlem len(notifyList)-1'e eşdeğerdir, son elemanın indeksini alır.
func notifyListAdd(l *notifyList) uint32 {
return l.wait.Add(1) - 1
}Gerçek ekleme işlemi notifyListWait fonksiyonunda tamamlanır:
func notifyListWait(l *notifyList, t uint32) {
...
}Bu fonksiyonda, ilk olarak bağlı listeyi kilitler, sonra mevcut goroutine'in zaten uyanıp uyanmadığını hızlıca kontrol eder. Eğer zaten uyandırılmışsa, doğrudan döner, engelleme ve bekleme yapmaz.
lockWithRank(&l.lock, lockRankNotifyList)
// Return right away if this ticket has already been notified.
if less(t, l.notify) {
unlock(&l.lock)
return
}Eğer uyandırılmamışsa, kuyruğa katılmak için bir sudog oluşturur, sonra gopark aracılığıyla askıya alır.
s := acquireSudog()
s.g = getg()
s.ticket = t
s.releasetime = 0
if l.tail == nil {
l.head = s
} else {
l.tail.next = s
}
l.tail = s
goparkunlock(&l.lock, waitReasonSyncCondWait, traceBlockCondWait, 3)Uyandırıldıktan sonra, sudog yapısını serbest bırakır:
releaseSudog(s)Signal
Signal engellenmiş goroutine'leri FIFO kuyruk sırasına göre uyandırır.
func (c *Cond) Signal() {
runtime_notifyListNotifyOne(&c.notify)
}Akışı şöyledir:
Kilitlemeden
l.wait'inl.notify'a eşit olup olmadığını kontrol eder. Eğer eşitse, tüm goroutine'lerin uyandırıldığı anlamına gelir.goif l.wait.Load() == atomic.Load(&l.notify) { return }Kilitledikten sonra, tekrar tümünün uyanıp uyanmadığını kontrol eder.
golockWithRank(&l.lock, lockRankNotifyList) t := l.notify if t == l.wait.Load() { unlock(&l.lock) return }l.notify'ı bir artırır.goatomic.Store(&l.notify, t+1)Bağlı listeyi dolaşır, uyandırılacak goroutine'i bulur ve son olarak
runtime.goreadyaracılığıyla goroutine'i uyandırır.gofor p, s := (*sudog)(nil), l.head; s != nil; p, s = s, s.next { if s.ticket == t { n := s.next if p != nil { p.next = n } else { l.head = n } if n == nil { l.tail = p } unlock(&l.lock) s.next = nil readyWithTime(s, 4) return } } unlock(&l.lock)
Broadcast
Broadcast tüm engellenmiş goroutine'leri uyandırır.
func (c *Cond) Broadcast() {
runtime_notifyListNotifyAll(&c.notify)
}Akışı temelde aynıdır:
Kilitsiz kontrol, tümü uyandırılmış mı.
go// Fast-path: if there are no new waiters since the last notification // we don't need to acquire the lock. if l.wait.Load() == atomic.Load(&l.notify) { return }Kilitledikten sonra, bağlı listeyi temizler, sonra kilidi serbest bırakır. Sonraki yeni gelen goroutine'ler liste başına eklenecektir.
golockWithRank(&l.lock, lockRankNotifyList) s := l.head l.head = nil l.tail = nil atomic.Store(&l.notify, l.wait.Load()) unlock(&l.lock)Bağlı listeyi dolaşır, tüm goroutine'leri uyandırır.
gofor s != nil { next := s.next s.next = nil readyWithTime(s, 4) s = next }
Özet
sync.Cond'un en yaygın kullanım durumları, çoklu goroutine'ler arasında belirli koşulların senkronize edilmesini gerektiren senaryolardır, tipik olarak üretici-tüketici modelleri, görev zamanlaması ve diğer senaryolara uygulanır. Bu senaryolarda, çoklu goroutine'ler yürütmeye devam etmeden önce belirli koşulların sağlanmasını beklemelidir veya koşullar değiştiğinde çoklu goroutine'leri bildirmelidir. Goroutine'ler arasındaki senkronizasyonu yönetmek için esnek ve verimli bir yol sağlar. Mutex kilitleriyle çalışarak, sync.Cond paylaşılan kaynaklara güvenli erişimi sağlayabilir ve belirli koşullar sağlandığında goroutine'lerin yürütme sırasını kontrol edebilir. İç uygulama prensiplerini anlamak, özellikle karmaşık koşul senkronizasyonu ile uğraşırken, eşzamanlı programlama tekniklerini daha iyi kavramamıza yardımcı olur.
