---
title: 'Bekleme Grupları'
source: 'https://academia.sh/tr/kurslar/go-eszamanlilik/bekleme-gruplari'
course: Eşzamanlılık
language: tr
updated: '2026-08-23T16:55:15+00:00'
license: 'CC BY-SA 4.0'
---

# Bekleme Grupları

Beş işçinin tamamlanmasını bir sayaç bekliyor: Add başlamadan önce sayacı beşe çıkarıyor, her işçi kendi Done'unu defer ile garanti ediyor, Wait sayaç sıfıra inince dönüyor — durma sınıfı yine beklediği geldi. Sayaç hiç sıfırlanmazsa Wait hiçbir zaman dönmüyor ve bu sızıntı bir boolean'la ölçülüyor; Done bir kez fazladan çağrıldığında sayaç eksiye düşüyor ve bekleme grubu panikliyor, panik recover ile yakalanıp yalnız panik=true yazılıyor. Sınırlayıcı ölçüm: beş işçinin hangisinin önce bittiği hiç ölçülmüyor, yalnız hepsinin bittiği ölçülüyor — sonuçlar toplanıp sıralanarak basılıyor.

Önceki ders tek bir kilidi bekleyen tek bir goroutine'i ölçtü. Ama paylaşılan durumu
koruyan tarafın kendisi çoğu zaman tek bir goroutine değil, bir **grup**: bir işi paylara
bölüp paralel yürüten birden çok goroutine, ve onların tamamının bitmesini bekleyen bir
ana goroutine. Kilit bu beklemeyi kurmuyor — kilit yalnız aynı anda kaç goroutine'in bir
kaynağa dokunabileceğini sınırlıyor, kaç goroutine'in **bittiğini** saymıyor. Bu derste
ölçülen araç bu sayımı yapan `sync.WaitGroup`, bir **bekleme grubu**.

Bir bekleme grubunu bir kanalla taklit etmek mümkün — her işçi bittiğinde bir kanala boş
bir değer gönderir, `main` de o kanaldan tam olarak işçi sayısı kadar değer okur. Önceki
konudaki işçi havuzu ve yayma-toplama dersleri gerçekten böyle yaptı: sonuç kanalının
kendisi hem değeri taşıyor hem de kaç sonucun geldiğini sayıyordu. Bekleme grubu bu iki
işi ayırıyor: kaç işçinin bittiğini saymak için ayrı bir sayaç, değeri taşımak için (bu
derste olduğu gibi) ayrı bir kanal ya da hiç kanal. Bu ayrım, işçilerin ürettiği bir
değer olmadığında — yalnız bir işin **bittiğini bilmek** yeterli olduğunda — daha az
düzenek gerektiriyor.

## Sayaç Sözleşmesi

Bekleme grubu bir dilim, bir kanal ya da bir liste değil — içinde tek bir tamsayı sayaç
tutan bir tip. Sözleşmesi üç çağrıya dayanıyor: `Add(n)` sayacı `n` artırıyor, `Done()`
sayacı bir azaltıyor, `Wait()` sayaç sıfıra inene kadar bloklanıyor. Bu üç çağrının doğru
sırayla kullanılması bir kural: `Add` her zaman ilgili goroutine **başlamadan önce**
çağrılmalı, `Done` her goroutine'in **her çıkış yolunda** çalışmalı — bu yüzden `defer` ile
yazılıyor, tıpkı önceki dersteki `Unlock` gibi — ve `Wait` sayaç sıfıra inmeden asla
dönmüyor.

`Add`in goroutine başlamadan önce çağrılması bir üslup tercihi değil. `Add` yerine
goroutine'in **kendi içinde**, ilk satırında çağrılsaydı, `main`in kendisi `grup.Wait()`e
o goroutine daha `Add`ini hiç çalıştırmadan ulaşabilirdi — bu durumda sayaç henüz sıfır
olduğu için `Wait` hemen dönebilir, henüz başlamamış bir işi hiç beklemeden. Bu, kod
kaynağına bakarak fark edilmesi güç bir hata sınıfı, çünkü hangi sırayla çalışacağı
goroutine zamanlamasına bağlı ve bu ders böyle bir kurulumu bilerek koşturmuyor — sonuç
koşumdan koşuma değişebileceği için bu kursun belirlenimcilik kısıtına girerdi. Kural
bunun yerine kaynağın kendisinde okunabilir: örnekteki `grup.Add(1)` satırı, döngünün
her adımında, `go` sözcüğünden önce duruyor.

```go
// main.go — sayac sozlesmesi, sifirlanmayan sayacta sizinti, sayacin sifirin altina inmesi
package main

import (
	"fmt"
	"runtime"
	"sort"
	"sync"
)

func main() {
	fmt.Println("-- sayac sozlesmesi: Add once, Done her yolda, Wait sifirda doner --")
	const isSayisi = 5
	var grup sync.WaitGroup
	tamamlanan := make(chan string, isSayisi)
	agirliklar := []int{3, 1, 4, 1, 5}
	for i := 0; i < isSayisi; i++ {
		grup.Add(1)
		go func(indeks, agirlik int) {
			defer grup.Done()
			toplam := 0
			for j := 0; j < agirlik*1000; j++ {
				toplam += j
			}
			tamamlanan <- fmt.Sprintf("is-%d (agirlik=%d, toplam=%d)", indeks, agirlik, toplam)
		}(i, agirliklar[i])
	}
	grup.Wait()
	fmt.Println("Wait dondu: durdu, beklediği geldi")

	close(tamamlanan)
	var raporlar []string
	for r := range tamamlanan {
		raporlar = append(raporlar, r)
	}
	sort.Strings(raporlar)
	for _, r := range raporlar {
		fmt.Println(r)
	}

	fmt.Println()
	fmt.Println("-- sifirlanmayan sayac: Add var, Done hic cagrilmiyor --")
	taban := runtime.NumGoroutine()
	var sizanGrup sync.WaitGroup
	sizanGrup.Add(1)
	bitti := make(chan struct{})
	go func() {
		sizanGrup.Wait()
		close(bitti)
	}()
	select {
	case <-bitti:
		fmt.Println("Wait dondu: beklenmiyordu")
	default:
		fmt.Println("Wait dondu mu: hayir, sayac hic sifirlanmadi")
	}
	fmt.Println("durmayan goroutine kaldi mi:", runtime.NumGoroutine() > taban)

	fmt.Println()
	fmt.Println("-- sayacin sifirin altina inmesi: panik --")
	panikVarMi := calistirVeKurtar(func() {
		var asiriGrup sync.WaitGroup
		asiriGrup.Add(1)
		asiriGrup.Done()
		asiriGrup.Done()
	})
	fmt.Println("panik:", panikVarMi)
}

// calistirVeKurtar verilen islevi calistirir, panikledi mi diye dondurur; yigit izi basilmaz.
func calistirVeKurtar(islev func()) (panikVarMi bool) {
	defer func() {
		if recover() != nil {
			panikVarMi = true
		}
	}()
	islev()
	return false
}
```

```
-- sayac sozlesmesi: Add once, Done her yolda, Wait sifirda doner --
Wait dondu: durdu, beklediği geldi
is-0 (agirlik=3, toplam=4498500)
is-1 (agirlik=1, toplam=499500)
is-2 (agirlik=4, toplam=7998000)
is-3 (agirlik=1, toplam=499500)
is-4 (agirlik=5, toplam=12497500)

-- sifirlanmayan sayac: Add var, Done hic cagrilmiyor --
Wait dondu mu: hayir, sayac hic sifirlanmadi
durmayan goroutine kaldi mi: true

-- sayacin sifirin altina inmesi: panik --
panik: true
```

**PD4.** Beş işçi beş farklı ağırlıkla (`3, 1, 4, 1, 5`) başlatılıyor, her biri kendi
toplamını hesaplayıp bir kanala yazıyor. `grup.Wait()` ancak beşi de kendi `Done`sunu
çağırdıktan sonra dönüyor — bu, kursun ilk dersinden beri işlenen durma sınıfının bir
örneği daha: `main`in kendisi bir goroutine gibi bekliyor, beklediği şey (sayacın sıfıra
inmesi) geldiğinde duruyor. Bekleme grubu burada kilitten farklı bir sözleşme sunuyor:
kilit tek bir kaynağı korurken, bekleme grubu **kaç işin bittiğini** sayıyor. Ortak
tanımın üçüncü iddiası bunu doğrudan destekliyor: beş "beklediği geldi" satırı beş ayrı
düzenekle kurulmuştu (tamponsuz alım, kapanan kanal, kilit, bekleme grubu, `select`) ve
düzenek değişse de bağımlılık değişmiyordu. Önceki ders kilidi, bu ders bekleme grubunu
kullanıyor — ikisi de aynı ailenin, "başka bir goroutine'in adımına bağlı olarak durma"
ailesinin, birer üyesi.

Ağırlıkların kendisi de bilerek farklı seçildi: `1` ağırlığı iki kez tekrarlanıyor
(`is-1` ve `is-3`), geri kalan üçü (`3, 4, 5`) birbirinden farklı. Bu, bekleme grubunun
işçileri **hiçbir şekilde** ayırt etmediğini gösteriyor — sayaç yalnız kaç `Done`
çağrıldığını tutuyor, hangi goroutine'in hangi işi yaptığını bilmiyor. `is-1` ile `is-3`
aynı ağırlıkla aynı toplamı (`499500`) üretiyor ve bekleme grubu için bu iki işçi
arasında hiçbir fark yok; ayrımı yalnız çıktıdaki `is-` önekleri taşıyor, bekleme grubunun
kendisi değil.

**PD5.** Beş işçi beş farklı sürede bitiyor — ağırlığı yüksek olan daha uzun döngü
çalıştırıyor — ama bu ders hangisinin **önce** bittiğini hiç ölçmüyor. Kanala yazılan
raporlar `tamamlanan` kanalından okunup bir dilime toplanıyor, sonra `sort.Strings` ile
sıralanıp basılıyor; kanaldan **geliş sırası** hiç yazdırılmıyor, çünkü o sıra goroutine
zamanlamasına bağlı ve koşumdan koşuma değişebilir. Bekleme grubunun garanti ettiği tek
şey sayacın sıfıra indiği an; hangi `Done` çağrısının önce geldiği bir garanti değil. Bu,
dersin sınırlayıcı ölçümü: **beş işçinin hepsi bitti** ölçülüyor, **hangisi önce bitti**
ölçülemiyor — ölçülmeye çalışılsaydı bu dersin de çıktısı koşumdan koşuma değişir ve
doğrulama sapma verirdi.

`grup` değişkeni `main`in yerel bir değişkeni ve her goroutine'e bir kapanış üzerinden
**işaretçi olmadan** erişiliyor — Go'nun kapanışları çevreleyen değişkenleri kendi
adresleriyle yakaladığı için `grup.Add`, `grup.Done` ve `grup.Wait` hep aynı sayaca
dokunuyor. Bekleme grubu bir yapı ve Go Temelleri kursunda ölçülen değerle aktarım
kuralı burada da geçerli: `grup`ün bir **kopyası** alınıp bir işleve değerle geçirilseydi,
o kopyanın kendi sayacı olurdu ve `main`in kendi sayacından bağımsız kalırdı — sözleşme
sessizce bozulurdu. İncelemenin görevi (Go Komut Ailesi dersinde ölçülen inceleyicinin
işi) tam olarak bunun gibi kalıpları yakalamak; bu ders böyle bir kopyalamayı bilerek
kurup koşturmuyor, çünkü bu, önceki dersteki kilidin dışına taşan tek satır gibi, kaynağı
okuyarak fark edilmesi güç bir hata sınıfı olurdu.

Kilit ile bekleme grubu burada iki ayrı sorumluluğu paylaşıyor. Önceki dersteki
`KorumaliSayac`, aynı değere birden çok goroutine'in aynı anda yazmasını engellemeyi
üstlenmişti; bu değeri kim koruyorsa koruyor olsun, "hepsi bitti mi" sorusuna yanıt
vermiyordu. Bu dersteki `tamamlanan` kanalı ile `grup` de tersini yapıyor: hangi işçinin
işi bittiğini sayıyor, ama işçilerin kendi aralarında paylaştığı bir belleğe hiç
karışmıyor — beş işçi burada zaten hiçbir ortak değişkene yazmıyor, her biri kendi yerel
`toplam`ını hesaplayıp kanala gönderiyor. Gerçek bir programda bu iki araç birlikte
görülüyor: birden çok goroutine ortak bir değeri kilitle koruyup her biri işini bitirince
`Done` çağırabiliyor, `main` da `Wait` ile hepsinin bitmesini bekleyip ancak o zaman
korunan değeri okuyabiliyor. Kilit **neyin** korunacağını, bekleme grubu **ne zaman**
okunmaya hazır olduğunu belirliyor; bu iki soru birbirinin yerini tutmuyor.

## Sıfırlanmayan Sayaç: Sızıntı

**PD6.** İkinci kurulum sözleşmeyi bilerek yarım bırakıyor: `sizanGrup.Add(1)` çağrılıyor
ama karşılığındaki `Done` hiçbir goroutine'de hiç çağrılmıyor. Sayaç bir daha asla sıfıra
inmeyeceği için `sizanGrup.Wait()`i çağıran goroutine sonsuza dek bloklu kalıyor — bu,
dördüncü durma sınıfının kendisi: **hiç durmadı**. Bu bloklanmayı beklemeden denetlemek
için `Wait`i ayrı bir goroutine içine alıp bir kapatma kanalıyla sarıyoruz; `select`in
`default` dalı, o kanaldan hiçbir zaman bir değer gelmeyeceğini — çünkü sayaç matematiksel
olarak asla sıfıra inemez — anında ve kesin biçimde gösteriyor. Bu bir zamanlama tahmini
değil: `default` dalının seçilmesi hiçbir koşumda değişmiyor, çünkü `Wait`in dönmesi
zamanlamaya değil sayacın değerine bağlı ve o değer hiç değişmiyor.

**PD7.** `runtime.NumGoroutine()` çağrısı taban sayıyla karşılaştırılıp yalnız bir
`true`/`false` üretiyor; kaç goroutine'in kaldığı ayrıca sayılmıyor. Bu, ortak tanımın
ikinci iddiasının bir başka örneği: durmamak Go çalışma zamanı tarafından hiçbir hata ya
da uyarıya dönüştürülmüyor, programın geri kalanı normal biçimde çalışmaya devam ediyor —
yalnız o tek goroutine hiçbir zaman bitmiyor. Bu sessizlik, Goroutine dersinde ölçülen
kanal sızıntılarıyla aynı aileden: bekleme grubu da kanal da, karşı tarafın sözleşmesini
yerine getirmemesi durumunda kendiliğinden bir hata üretmiyor.

Bekleme grubunun kendisinde bir iptal mekanizması yok — `Wait` çağrısını dışarıdan
durdurmanın bir yolu bulunmuyor, sayaç sıfıra inene kadar bekler. Bağlam paketinin
zaman aşımı dersinde ölçülen `ctx.Done()` deseni bu boşluğu dolduruyor, ama bekleme
grubunun kendisi böyle bir çıkış sunmuyor; bu yüzden gerçek bir programda sonsuz
bekleme riski taşıyan bir `Wait` çağrısı, çoğunlukla bir zaman aşımıyla birlikte, ayrı bir
goroutine ve kanal üzerinden — tam olarak bu derste `bitti` kanalıyla yapıldığı gibi —
sarmalanıyor. Bu ders o sarmalamayı yalnızca sızıntıyı denetlemek için kullandı; gerçek bir
zaman aşımı kurup beklemeyi kesmek B payının konusuydu ve burada tekrarlanmıyor.

## Sayacın Sıfırın Altına İnmesi: Panik

**PD8.** Üçüncü kurulum tam tersi bir hatayı modelliyor: `Add(1)` bir kez çağrılıyor ama
`Done()` iki kez çağrılıyor. Bekleme grubu bunu sessizce görmezden gelmiyor — sayaç
negatife düştüğü anda **panikliyor**, çünkü negatif bir sayaç kütüphanenin kendi iç
tutarlılığını bozuyor ve bu durumu susturmak yerine hemen bildirmek Go standart
kütüphanesinin seçimi. `calistirVeKurtar`, önceki derslerin kalıbını sürdürerek paniği
`recover` ile yakalıyor ve yalnız `panik=true`/`panik=false` yazıyor; panik metninin
kendisi hiçbir yerde basılmıyor, çünkü o metin bir yığıt izi taşıyor.

Panik metninin basılmaması yalnız bir yığıt izi kaygısı değil. Standart kütüphanenin bu
paniğe verdiği metin, kaynağı derleyen sürüme bağlı olarak değişebilir — kelimesi kelimesine
aynı kalacağının garantisi yok, çünkü bu metin dilin belirttiği bir sözleşmenin parçası
değil, kütüphanenin iç uygulamasının bir ayrıntısı. `panik=true`/`panik=false` bunun
yerine dilin garanti ettiği tek şeyi ölçüyor: sayaç negatife düştüğünde bir panik
**oluyor mu, olmuyor mu**. Kanal Kapatma ve Aralık dersinde kapalı bir kanala ikinci kez
gönderim yapmanın da aynı biçimde ölçülmesi bu kursun tekrar eden bir alışkanlığı: paniğin
kendisi ölçülüyor, paniğin metni değil.

Bu iki hatalı kurulum ilginç bir karşıtlık kuruyor: sayacın hiç sıfırlanmaması **sessiz**
kalıyor, sayacın fazla sıfırlanması ise **yüksek sesle** bildiriliyor. Bu fark bekleme
grubunun kendi tasarımından geliyor, iki hatanın önemine dair bir yargı değil. Sayaç
sıfırın altına inebilir çünkü bekleme grubu her `Done` çağrısında sayacı denetliyor ve
negatif bir değeri hemen yakalıyor; sayacın hiç sıfırlanmaması ise bekleme grubunun
denetleyebileceği bir durum değil — kütüphane, kaç `Add` çağrısının karşılıksız kaldığını
bilemez, çünkü "karşılıksız kalmak" sonsuza kadar sürebilecek bir bekleyiş, bir hata değil.
Panik anlık bir olay olduğu için yakalanabiliyor; sızıntı bir olay değil, bitmeyen bir
durum olduğu için yakalanamıyor, yalnız dışarıdan **denetlenebiliyor**.

Bu üç kurulum bekleme grubunun sözleşmesini üç yönden sınıyor: sayaç doğru sıfırlandığında
`Wait` durur, hiç sıfırlanmadığında sonsuza dek durmuyor, fazla sıfırlandığında panikliyor.
Sözleşmenin üç ihlali de aynı kökten geliyor — `Add` ile `Done` çağrılarının sayısının
birbirine denk düşmesi gerekiyor, ve bu denkliği derleyici değil çalışma zamanı
denetliyor. Önceki derste kilidin sözleşmesi neyin **kilit altında** kalması gerektiğiydi;
burada sözleşme kaç `Add`in kaç `Done`a karşılık geldiği. İkisi de derleyicinin hiç
göremediği bir kural: bir kaynağı okuyan biri, `Add` ile `Done` çağrılarının sayıca eşit
olup olmadığını yalnız kaynağı **izleyerek**, çalıştırarak görebiliyor.

## Özet

- `sync.WaitGroup`, `Add`, `Done` ve `Wait` üçlüsüyle çalışan bir sayaç; `Wait` sayaç
  sıfıra inene kadar bloklanıyor ve durma sınıfı beklediği geldi.
- Beş işçinin tamamlanma sırası hiç ölçülmüyor, yalnız hepsinin bittiği ölçülüyor;
  sonuçlar bir kanaldan toplanıp sıralanarak basılıyor.
- Sayaç hiç sıfırlanmazsa `Wait` sonsuza dek bloklanıyor; bu, engellenmeden `select`in
  `default` dalıyla denetlendi ve sızıntı yalnız bir boolean olarak ölçüldü.
- Sayacı sıfırın altına indiren fazladan bir `Done` çağrısı bekleme grubunu paniklettiriyor;
  panik `recover` ile yakalandı, yalnız `panik=true` yazıldı.
- Kilidin sözleşmesi neyin korunacağıydı, bekleme grubunun sözleşmesi kaç `Add`in kaç
  `Done`a karşılık geldiği — ikisi de derleyicinin denetlemediği, yalnız koşturarak
  görülebilen kurallar.

## Sonraki Adım

Bekleme grubu bir sayı sayıyor ama tek başına hiçbir değeri korumuyor ve hiçbir işlemi
tek seferle sınırlamıyor. İki ders boyunca paylaşılan durumu korumanın tek aracı kilitti;
sıradaki ders bu tekelin doğru olmadığını gösteriyor ve paylaşılan durumun iki ayrı
ilkelini ölçüyor: bir gövdenin kaç goroutine çağırırsa çağırsın yalnız bir kez
çalışmasını garanti eden bir kez çalıştırma, ve tek bir değişkeni kilit kullanmadan
koruyan atomik işlemler. Her iki ilkel de kendi dar sözleşmesiyle geliyor — biri yalnız
bir defalık bir kurulumu, öbürü yalnız tek bir değişkeni koruyor — ve bu darlığın nerede
yeterli, nerede yetersiz kaldığı sıradaki dersin kendi sınırlayıcı ölçümü.
