sync.Cond
türünü, kullanım örneklerini ve ne zaman kullanılacağını tartışmak istiyorum.
sync.Cond
nedir? Go programlama dilinde, sync.Cond
bir koşul değişkenini temsil eden, sync
paketinde tanımlanan bir türdür. Koşul değişkenleri, devam etmeden önce belirli bir koşulun gerçekleşmesini beklemelerine izin vererek goroutinleri koordine etmek için kullanılan senkronizasyon ilkelleridir.
sync.Cond
türü, koşul değişkenlerini oluşturmanın ve yönetmenin bir yolunu sağlar. Üç ana yöntemi vardır:
Wait()
: Bu yöntem, çağıran goroutine'in, başka bir goroutine durum değişkenine sinyal gönderene kadar beklemesine neden olur. Goroutine Wait()
öğesini çağırdığında, ilgili kilidi serbest bırakır ve başka bir goroutine aynı sync.Cond
değişkeninde Signal()
veya Broadcast()
öğesini çağırana kadar yürütmeyi askıya alır.
Signal()
: Bu yöntem, koşul değişkeninde bekleyen bir goroutine'i uyandırır. Birden fazla goroutin bekliyorsa bunlardan yalnızca biri uyandırılır. Hangi goroutine'in uyandırılacağı seçimi keyfidir ve garanti edilmez.
Broadcast()
: Bu yöntem, koşul değişkenini bekleyen tüm goroutinleri uyandırır. Broadcast()
çağrıldığında, bekleyen tüm goroutinler uyandırılır ve ilerleyebilir.
sync.Cond
koşul değişkenine erişimi senkronize etmek için ilişkili bir sync.Mutex
gerektirdiğini unutmayın.
sync.Cond
kullanarak, goroutinlerin belirli koşullara göre yürütülmesini koordine edebilir, Go'da daha kontrollü ve senkronize eş zamanlı programlamaya olanak tanıyabilirsiniz.
sync.Cond
, goroutinlerin belirli koşullara göre birbirleriyle koordine olması ve iletişim kurması gereken senaryolarda yaygın olarak kullanılır. sync.Cond
için yaygın kullanım örneklerini ele alalım.
sync.Cond
birden fazla goroutine'in yürütülmesini senkronize etmek için kullanılabilir. Örneğin, devam etmeden önce belirli bir koşulun karşılanmasını beklemesi gereken çeşitli goroutinleriniz olabilir. Bekleyen goroutinler cond.Wait()
çağırabilir ve sinyal veren goroutine, koşul karşılandığında bekleyen goroutinleri uyandırmak için cond.Signal()
veya cond.Broadcast()
çağırabilir.
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) wg.Add(2) go func() { fmt.Println("Goroutine 1 is started") defer wg.Done() cond.L.Lock() defer cond.L.Unlock() fmt.Println("Goroutine 1 is waiting for condition") cond.Wait() fmt.Println("Goroutine 1 met the condition") fmt.Println("Goroutine 1 is done") }() go func() { fmt.Println("Goroutine 2 is started") defer wg.Done() time.Sleep(5 * time.Second) // Simulating some work cond.L.Lock() defer cond.L.Unlock() fmt.Println("Goroutine 2 is signaling condition") cond.Signal() fmt.Println("Goroutine 2 completed signaling") fmt.Println("Goroutine 2 is done") }() wg.Wait() }
Bu örnekte iki goroutinimiz var. İlk goroutine cond.Wait()
kullanarak bir koşulu beklerken, ikinci goroutine cond.Signal()
kullanarak koşulu bildirir.
Program çalıştırıldığında, ilk goroutine kilidi alır ve ardından cond.Wait()
öğesini çağırır. Koşul henüz karşılanmadığından, ilk goroutin kilidi serbest bırakır ve yürütülmesini askıya alır.
Bu arada, ikinci goroutine bazı işleri simüle ederek beş saniye boyunca uyur. Kilidi alır ve ardından cond.Signal()
öğesini çağırır. Bekleyen goroutine'i uyandırır ve daha sonra kilidi alır ve çalıştırır.
sync.Cond
kullanılması, ilk goroutin'in ikinci goroutin durumu işaret edene kadar beklemesini sağlar ve iki goroutin arasında senkronizasyon ve koordinasyona izin verir.
sync.Cond
, ortak bir sabit boyutlu arabellek veya kuyruğu paylaşan iki tür işlemi (üreticileri ve tüketicileri) içeren klasik bir senkronizasyon sorunu olan üretici-tüketici sorununu çözmede yararlı olabilir. Üretici goroutinleri, tüketim için yeni veriler mevcut olduğunda tüketici goroutinlerini bilgilendirmek için cond.Signal()
veya cond.Broadcast()
işlevini kullanabilir.
package main import ( "fmt" "sync" "time" ) const MaxMessageChannelSize = 5 func main() { var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) messageChannel := NewMessageChannel(MaxMessageChannelSize) producer := NewProducer(cond, messageChannel) consumer := NewConsumer(cond, messageChannel) wg.Add(2) go func() { defer wg.Done() for i := range 10 { producer.Produce(fmt.Sprintf("Message %d", i)) } }() go func() { defer wg.Done() for range 10 { consumer.Consume() } }() wg.Wait() } type MessageChannel struct { maxBufferSize int buffer []string } func NewMessageChannel(size int) *MessageChannel { return &MessageChannel{ maxBufferSize: size, buffer: make([]string, 0, size), } } func (mc *MessageChannel) IsEmpty() bool { return len(mc.buffer) == 0 } func (mc *MessageChannel) IsFull() bool { return len(mc.buffer) == mc.maxBufferSize } func (mc *MessageChannel) Add(message string) { mc.buffer = append(mc.buffer, message) } func (mc *MessageChannel) Get() string { message := mc.buffer[0] mc.buffer = mc.buffer[1:] return message } type Producer struct { cond *sync.Cond messageChannel *MessageChannel } func NewProducer(cond *sync.Cond, messageChannel *MessageChannel) *Producer { return &Producer{ cond: cond, messageChannel: messageChannel, } } func (p *Producer) Produce(message string) { time.Sleep(500 * time.Millisecond) // Simulating some work p.cond.L.Lock() defer p.cond.L.Unlock() for p.messageChannel.IsFull() { fmt.Println("Producer is waiting because the message channel is full") p.cond.Wait() } p.messageChannel.Add(message) fmt.Println("Producer produced the message:", message) p.cond.Signal() } type Consumer struct { id int cond *sync.Cond messageChannel *MessageChannel } func NewConsumer(cond *sync.Cond, messageChannel *MessageChannel) *Consumer { return &Consumer{ cond: cond, messageChannel: messageChannel, } } func (c *Consumer) Consume() { time.Sleep(1 * time.Second) // Simulating some work c.cond.L.Lock() defer c.cond.L.Unlock() for c.messageChannel.IsEmpty() { fmt.Println("Consumer is waiting because the message channel is empty") c.cond.Wait() } message := c.messageChannel.Get() fmt.Println("Consumer consumed the message:", message) c.cond.Signal() }
Bu örnekte, mesajlar üreten ve bunları mesaj kanalına ekleyen bir üretici goroutine ve mesajları tüketen bir tüketici goroutine sahibiz. Mesaj kanalının MaxMessageChannelSize
tarafından tanımlanan maksimum boyutu vardır.
Üretici goroutine, mesaj kanalına mesajlar ekler ve yeni veriler mevcut olduğunda tüketici goroutine'i bilgilendirmek için cond.Signal()
kullanır. Mesaj kanalı doluysa, üretici goroutine, tüketici bazı verileri tüketip mesaj kanalında yer açana kadar cond.Wait()
kullanarak bekler.
Benzer şekilde, tüketici goroutine'i mesaj kanalından gelen mesajları tüketir ve mesaj kanalında boş alan oluştuğunda üretici goroutine'i bilgilendirmek için cond.Signal()
kullanır. Boşsa tüketici goroutine, üretici bazı veriler üretip bunu mesaj kanalına ekleyene kadar cond.Wait()
kullanarak bekler.
Burada sync.Cond
, üretici ve tüketici gorutinleri arasında koordinasyon ve senkronizasyona olanak sağlar. Mesaj kanalı boşaldığında tüketicinin, dolduğunda ise üreticinin beklemesini sağlayarak üretici-tüketici problemini çözer.
Birden fazla goroutin'in paylaşılan bir kaynağa özel erişime ihtiyacı olduğunu varsayalım. sync.Cond
erişimi koordine etmek için kullanılabilir. Örneğin, bir çalışan goroutin havuzunun, işleme başlayabilmesi için belirli sayıda kaynağın kullanılabilir hale gelmesini beklemesi gerekebilir. Goroutinler cond.Wait()
kullanarak koşul değişkenini bekleyebilir ve cond.Signal()
veya cond.Broadcast()
kullanarak kaynağın serbest bırakılması konusunda bildirimde bulunabilir.
package main import ( "fmt" "sync" "time" ) const MaxResources = 3 func main() { var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) resourceProvider := NewResourceProvider(cond, MaxResources) wg.Add(10) for i := range 10 { go func(workerID int) { defer wg.Done() worker := NewWorker(workerID, cond, resourceProvider) worker.Run() }(i) } wg.Wait() } type ResourceProvider struct { maxResources int availableResources int cond *sync.Cond } func NewResourceProvider(cond *sync.Cond, maxResources int) *ResourceProvider { return &ResourceProvider{ cond: cond, availableResources: maxResources, } } func (rp *ResourceProvider) AvailableResources() int { return rp.availableResources } func (rp *ResourceProvider) AcquireResoirce() { rp.availableResources-- } func (rp *ResourceProvider) ReleaseResource() { rp.availableResources++ } type Worker struct { id int cond *sync.Cond rp *ResourceProvider } func NewWorker(workerID int, cond *sync.Cond, rp *ResourceProvider) *Worker { return &Worker{ id: workerID, cond: cond, rp: rp, } } func (w *Worker) Run() { w.cond.L.Lock() for w.rp.AvailableResources() == 0 { fmt.Printf("Worker %d is waiting for resources\n", w.id) w.cond.Wait() } w.rp.AcquireResoirce() fmt.Printf("Worker %d acquired resource. Remaining resources: %d\n", w.id, w.rp.AvailableResources()) w.cond.L.Unlock() time.Sleep(1 * time.Second) // Simulating work w.cond.L.Lock() defer w.cond.L.Unlock() w.rp.ReleaseResource() fmt.Printf("Worker %d released resource. Remaining resources: %d\n", w.id, w.rp.AvailableResources()) w.cond.Signal() }
Bu örnekte, sınırlı kaynaklara özel erişime ihtiyaç duyan birden fazla çalışan goroutine'imiz var. Çalışan gorutinleri, diğer çalışanlarla koordinasyon sağlamak için cond.Signal()
işlevini kullanarak kaynakları alır ve serbest bırakır. Hiçbir kaynak mevcut değilse, çalışan goroutinler using cond.Wait()
diğer goroutine kaynağı serbest bırakana kadar bekler.
Bu örnekte, sync.Cond
, çalışan goroutinleri arasında senkronizasyona ve koordinasyona izin vererek çalışan goroutinlerin hiçbir kaynak olmadığında beklemesini sağlar ve böylece kaynak erişimini etkili bir şekilde senkronize eder.
sync.Cond
, goroutine'leri sistemdeki belirli olaylar veya değişiklikler hakkında bilgilendirmek için kullanılabilir. Örneğin, belirli bir olayı bekleyen gorutinleriniz olabilir. Olay gerçekleştiğinde, sinyal veren goroutin, bekleyen goroutinleri uyandırmak ve onların olayı yönetmesine izin vermek için cond.Signal()
veya cond.Broadcast()
kullanabilir.
package main import ( "fmt" "sync" "time" ) const maxWorkersCount = 10 func main() { var counter int32 var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) wg.Add(maxWorkersCount) for i := range maxWorkersCount { go func(workerID int) { defer wg.Done() fmt.Printf("Worker %d performing work\n", workerID) time.Sleep(1 * time.Second) // Simulate work cond.L.Lock() defer cond.L.Unlock() counter++ if counter == maxWorkersCount { fmt.Println("All workers have reached the barrier") cond.Broadcast() } else { fmt.Printf("Worker %d is waiting at the barrier\n", workerID) cond.Wait() } fmt.Printf("Worker %d passed the barrier\n", workerID) }(i) } wg.Wait() }
Burada, işi gerçekleştiren ve bir bariyer noktasında senkronize olan birden fazla işçi gorutinimiz var. Çalışan gorutinleri bir sayacı artırır ve ardından ya bariyerde bekler ya da bariyere ulaşan işçi sayısına bağlı olarak cond.Wait()
ve cond.Broadcast()
kullanarak bariyere sinyal verir.
Her çalışan goroutine bir miktar iş yapar ve ardından sayaç değişkenini artırmak için kilidi alır. Mevcut çalışan bariyere ulaşan son kişi ise, tüm bekleyen çalışanları uyandırmak için cond.Broadcast()
işlevini kullanarak bariyer durumunu yayınlar. Aksi takdirde, son işçi tarafından bilgilendirilmek üzere cond.Wait()
kullanarak bariyerde bekler.
Bariyer senkronizasyonu, tüm çalışan goroutinlerinin, herhangi biri bariyerin ötesine geçmeden önce bariyere ulaşmasını sağlar. İş akışındaki belirli bir noktada birden fazla goroutin yürütülmesinin senkronize edilmesini gerektiren senaryolarda yararlı olabilir.
Bu örnekte bariyerin basit bir sayaç kullanılarak uygulandığına dikkat edin. Ancak daha karmaşık senaryolarda, doğru senkronizasyonu sağlamak ve yarış koşullarından kaçınmak için ek senkronizasyon mekanizmalarını veya koşullarını dikkate almanız gerekebilir.
Sonuç olarak, sync.Cond
, Go programlama dilinde, belirli koşullara dayalı olarak goroutinler arasında senkronizasyon ve koordinasyona izin veren kullanışlı bir türdür. Koşul değişkenlerini oluşturmanın ve yönetmenin bir yolunu sağlar. Bekleme yöntemleri, sinyal ve yayın koşulları vardır. Sync.Cond'u kullanarak Go'da daha kontrollü ve senkronize eş zamanlı programlar yazabilirsiniz.
sync.Cond
Go standart kütüphanesi tarafından sağlanan senkronizasyon temellerinden yalnızca biri olduğunu ve kullanımının eşzamanlı programınızın özel gereksinimlerine bağlı olduğunu unutmamak önemlidir. Bazı durumlarda kanallar veya sync.WaitGroup
gibi diğer senkronizasyon temelleri daha uygun olabilir.