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

# Eşzamanlılık İlkelleri

Otuz goroutine aynı ilklendirmeyi çağırıyor ama bir kez çalıştırma sözleşmesi gövdenin tam olarak bir kez koşmasını garanti ediyor. Otuz goroutine, her biri beş yüz artışla, aynı sayacı bir kez atomik işlemlerle bir kez kilitle artırıyor; ikisi de 15000 veriyor. Sınırlayıcı ölçüm, iki ayrı atomik değişkenin (bakiye ve işlem sayısı) her biri kendi başına tutarlı kalırken bir arada okunduklarında tutarsız bir çift verebileceğini kanalla zorlanmış bir sırayla modelliyor: okuyan taraf bakiye=100, işlemSayısı=0 görüyor.

Önceki dersin kapanışı bir sınır çizdi: kilit iki ders boyunca paylaşılan durumu korumanın
tek aracıymış gibi göründü, ama değil. Bu ders aynı korumayı iki ayrı ilkelle kuruyor —
`sync.Once`, bir gövdenin kaç kez çağrılırsa çağrılsın yalnız bir kez koşmasını garanti
eden **bir kez çalıştırma**; ve `sync/atomic`, tek bir değişken üzerindeki işlemleri
kilit kullanmadan güvenli kılan **atomik işlemler**. İkisi de kilidin yaptığı işi
yapıyor gibi görünüyor, ama ikisinin de sözleşmesi kilitten daha dar: kilit herhangi bir
kod parçasını sarabiliyordu, bu iki ilkelin her biri yalnız tek bir dar durumu kapsıyor.

Ortak tanımın üçüncü iddiası bu derste de geçerli kalıyor: durduran şey aracın adı değil,
başka bir goroutine'in attığı bir adım. Bir kez çalıştırmada bu adım ilk çağrının gövdeyi
bitirmesi — bekleyen goroutine'ler kilitteki gibi beklediği geldi sınıfına giriyor. Atomik
işlemde ise hiç bekleme yok: atomik bir çağrı hiçbir goroutine'in bir şey yapmasını
beklemeden, hiç bloklanmadan döner — kendi başına biten bir adım, kimseye bağlı değil.
İkisi de kilidin **tek bir dar biçimini** temsil ediyor, ama biri hâlâ bekletiyor, öbürü
hiç bekletmiyor.

## Bir Kez Çalıştırma

Bir programın başlangıcında yalnız bir kez çalışması gereken bir adım — bir bağlantı
havuzunu kurmak, bir yapılandırmayı okumak — birden çok goroutine tarafından aynı anda
istenebilir. `sync.Once` bu isteği tek bir çağrıya indiriyor: `Do` yöntemine verilen
işlev, kaç goroutine `Do`yu çağırırsa çağırsın, ömür boyu **tam olarak bir kez** koşuyor;
sonraki her çağrı hiçbir şey yapmadan hemen dönüyor. `sync.Once`in sıfır değeri de tıpkı
kilit gibi kullanıma hazır: `var bir sync.Once` yazmak yeterli, ayrı bir kurulum
gerekmiyor — çünkü `Once`in içinde, ilk çağrının olup olmadığını izleyen bir sayaç ve o
sayacı koruyan kendi kilidi zaten var. `Once`in kendisi, bir önceki dersin ilkelini
kullanarak kurulmuş bir araç.

Aynı `bir` değişkenine ait `Do` çağrılarına **farklı** işlevler verilse bile sonuç
değişmiyor — koşan yalnız **ilk** çağrının işlevi, sonraki çağrılara verilen işlevler hiç
çalışmadan atlanıyor. Bu örnekte otuz goroutine aynı işlevi verdiği için bu ayrım
görünmüyor, ama sözleşme bunu zaten garanti ediyor: `Once`in hafızası "hangi işlev
verildi" değil, yalnız "bir kez çağrıldım mı" sorusuna verilen tek bitlik bir yanıt.
Hangi goroutine'in kendi `Do` çağrısının gövdeyi gerçekten koşturduğu da ölçülmüyor ve
ölçülemez — otuz goroutine aynı anda `Do`yu çağırıyor, hangisinin "kazandığı" zamanlamaya
bağlı; ama hepsi aynı işlevi verdiği için bu belirsizlik, birinci dersin kilitsiz
kurulumunda olduğu gibi, sonucu hiç etkilemiyor.

```go
// main.go — bir kez calistirma, atomik sayac ile kilitli sayacin karsilastirilmasi, cift atomik degiskenin modellenmesi
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	fmt.Println("-- bir kez calistirma: N goroutine cagiriyor, govde bir kez kosuyor --")
	var bir sync.Once
	var kacKezKostu int64
	var ilkDeger int64
	const goroutineSayisi = 30
	var grup sync.WaitGroup
	grup.Add(goroutineSayisi)
	for i := 0; i < goroutineSayisi; i++ {
		go func() {
			defer grup.Done()
			bir.Do(func() {
				atomic.AddInt64(&kacKezKostu, 1)
				atomic.StoreInt64(&ilkDeger, 42)
			})
		}()
	}
	grup.Wait()
	fmt.Printf("govde kac kez kostu: %d\n", kacKezKostu)
	fmt.Printf("ilklendirilen deger: %d\n", ilkDeger)

	fmt.Println()
	fmt.Println("-- atomik sayac ile kilitli sayac ayni sonucu veriyor mu --")
	const artisSayisi = 500
	var atomikSayac int64
	var grup2 sync.WaitGroup
	grup2.Add(goroutineSayisi)
	for i := 0; i < goroutineSayisi; i++ {
		go func() {
			defer grup2.Done()
			for j := 0; j < artisSayisi; j++ {
				atomic.AddInt64(&atomikSayac, 1)
			}
		}()
	}
	grup2.Wait()

	var kilit sync.Mutex
	kilitliSayac := 0
	var grup3 sync.WaitGroup
	grup3.Add(goroutineSayisi)
	for i := 0; i < goroutineSayisi; i++ {
		go func() {
			defer grup3.Done()
			for j := 0; j < artisSayisi; j++ {
				kilit.Lock()
				kilitliSayac++
				kilit.Unlock()
			}
		}()
	}
	grup3.Wait()

	fmt.Printf("atomik sayac  : %d\n", atomikSayac)
	fmt.Printf("kilitli sayac : %d\n", kilitliSayac)
	fmt.Printf("ikisi ayni mi : %v\n", atomikSayac == int64(kilitliSayac))

	fmt.Println()
	fmt.Println("-- sinirlayici olcum: iki atomik degiskenin birlikte tutarliligi modelleniyor --")
	var bakiye, islemSayisi int64
	adim1 := make(chan struct{})
	bitti := make(chan [2]int64)

	go func() {
		<-adim1
		okunanBakiye := atomic.LoadInt64(&bakiye)
		okunanIslem := atomic.LoadInt64(&islemSayisi)
		bitti <- [2]int64{okunanBakiye, okunanIslem}
	}()

	atomic.AddInt64(&bakiye, 100)
	adim1 <- struct{}{}
	ara := <-bitti
	atomic.AddInt64(&islemSayisi, 1)

	fmt.Printf("yazan taraf tamamladiginda bakiye=%d islemSayisi=%d\n",
		atomic.LoadInt64(&bakiye), atomic.LoadInt64(&islemSayisi))
	fmt.Printf("okuyan tarafin arada gordugu   bakiye=%d islemSayisi=%d (tutarsiz cift)\n",
		ara[0], ara[1])
}
```

```
-- bir kez calistirma: N goroutine cagiriyor, govde bir kez kosuyor --
govde kac kez kostu: 1
ilklendirilen deger: 42

-- atomik sayac ile kilitli sayac ayni sonucu veriyor mu --
atomik sayac  : 15000
kilitli sayac : 15000
ikisi ayni mi : true

-- sinirlayici olcum: iki atomik degiskenin birlikte tutarliligi modelleniyor --
yazan taraf tamamladiginda bakiye=100 islemSayisi=1
okuyan tarafin arada gordugu   bakiye=100 islemSayisi=0 (tutarsiz cift)
```

**PD9.** Otuz goroutine aynı `bir.Do(...)` çağrısını neredeyse aynı anda yapıyor, ama
`kacKezKostu` yalnız `1`. `sync.Once`in sözleşmesi burada bekleme grubundan farklı bir
şekilde çalışıyor: bekleme grubu bir sayacı **dışarıdan** artırıp azaltan çağrılara
dayanıyordu, `Once` ise kendi içinde bir kez çalıştıysa bir daha hiç çalışmayacağını
**kendisi** kararlaştırıyor. Otuz çağrıdan yalnız biri gövdeyi gerçekten koşturuyor,
geri kalan yirmi dokuzu `Do`nun döndüğü anı — gövde daha önce koşmuşsa hemen, hâlâ
koşuyorsa gövde bitene kadar — bekliyor. Bu da beklediği geldi sınıfının bir başka
düzeneği: bekleyen goroutine'ler kendi başlarına durmuyor, ilk çağrının gövdeyi bitirmesi
onları serbest bırakıyor.

`ilkDeger`in çıktısı bu garantinin ikinci yarısını gösteriyor: yalnız gövdenin **bir kez**
koşması yetmiyor, gövdenin yazdığı değerin **her** goroutine tarafından görülebilmesi de
gerekiyor. Otuz goroutine'den yalnız biri `ilkDeger`e `42` yazıyor, ama `Do`dan dönen
her goroutine — kendi çağrısı gövdeyi koşturmuş olsun ya da olmasın — aynı `42`yi okuyor.
Bu, `sync.Once`in kendi içindeki senkronizasyonun bir sonucu: `Do`dan dönmek, yalnız
"gövde bir yerde koştu" bilgisini değil, gövdenin yazdığı her değeri de beraberinde
taşıyor.

**PD10.** İkinci ölçüm aynı toplamı iki ayrı yoldan üretiyor: `atomikSayac`,
`atomic.AddInt64` ile korunuyor ve hiçbir kilide dokunmuyor; `kilitliSayac`, önceki
dersteki gibi `Lock`/`Unlock` çiftiyle korunuyor. Otuz goroutine, her biri beş yüz artışla,
toplam on beş bin artış uyguluyor ve iki sayaç da tam olarak `15000`e ulaşıyor. Bu eşitlik
birinci dersteki kilitli/kilitsiz karşılaştırmasından farklı: orada kilitsiz kurulum
paylaşılan tek bir belleğe hiç dokunmuyordu, burada `atomikSayac` gerçekten paylaşılan tek
bir bellek konumu ve otuz goroutine ona gerçekten aynı anda yazıyor. Atomik işlem
paylaşılan durum yarışını **koşturmadan değil, güvenli kılarak** dışlıyor — donanımın
sağladığı bölünmez oku-değiştir-yaz işlemiyle, kilit gerekmeden.

**M01/K05 İşletim Sistemi Kavramları**'nda kapanan atomik işlem kavramı burada aynen
geçerli: bir işlemin bölünmez olması, araya başka hiçbir goroutine'in giremeyeceği anlamına
geliyor. Go'nun `sync/atomic` paketi bu bölünmezliği belirli tamsayı ve işaretçi tipleri
için donanım desteğiyle sağlıyor; `AddInt64` çağrısının kendisi hiçbir zaman yarım
kalmıyor, bir goroutine okuma ile yazma arasına asla giremiyor.

Bu güvence bir bedelle geliyor: `sync/atomic`in koruduğu şey yalnız `int32`, `int64`,
`uint32`, `uint64`, işaretçi gibi belirli birkaç tip, ve her çağrı bu tiplerden **tek
birinin** üzerinde çalışıyor. Kilit böyle bir sınır tanımıyordu — `Lock`/`Unlock` çifti
arasına bir yapı, bir dilim, bir sözlük, ya da birden fazla değişkeni güncelleyen
keyfi uzunlukta bir kod parçası girebiliyordu. Atomik işlemin dar sözleşmesi tam olarak bu
esnekliğin bir kısmından vazgeçmenin karşılığı: tek bir tamsayıyı korumak için kilide hiç
gerek kalmıyor, ama korunan şey bir tamsayının ötesine geçtiği anda atomik işlem yetersiz
kalıyor.

`sync/atomic` bu derste kullanılan `AddInt64` ve `LoadInt64`in yanında bir de karşılaştırıp
değiştirme çağrısı taşıyor: verilen değişken beklenen değere eşitse yeni değeri yazıyor,
değilse hiçbir şey yapmadan bunu bildiriyor — okuma ile yazma arasına hiçbir goroutine'in
giremeyeceği bir başka bölünmez işlem. Bu ders o çağrıyı koşturmuyor; burada önemli olan
`sync/atomic`in tek bir tamsayı üzerindeki her işlemi (ekleme, okuma, yazma, koşullu
değiştirme) aynı bölünmezlik garantisiyle sunduğu, ama sunduğu şeyin her zaman **tek bir**
değişkenle sınırlı kaldığı.

## Sınırlayıcı Ölçüm: İki Atomik Değişkeni Birlikte Tutmak

**PD11.** Üçüncü kurulum tek bir değişken yerine iki tanesini — `bakiye` ve
`islemSayisi` — kullanıyor ve ikisi de kendi başına atomik olarak okunup yazılıyor. Ama
aralarında hiçbir birleşik koruma yok: yazan taraf önce `bakiye`yi güncelliyor, sonra
`islemSayisi`ni; bu iki güncelleme arasında kalan bir an, dışarıdan bakan bir goroutine'e
**tutarsız bir çift** gösterebiliyor. Bu boşluk kanalla zorlanan bir sırayla modelleniyor:
okuyan goroutine `adim1` kanalından sinyal bekliyor, `main` önce `bakiye`yi 100 yapıp
sinyali gönderiyor, okuyan goroutine tam bu anda hem `bakiye`yi hem `islemSayisi`ni
okuyor — `bakiye=100` ama `islemSayisi` henüz `0`. `main` ancak bundan sonra
`islemSayisi`ni artırıyor.

Bu, önceki dersteki kilit kapsamı modeliyle aynı ailede bir sınır, ama farklı bir yerde
duruyor: orada sorun kilidin **kapsamının** yanlış çizilmesiydi, burada iki değişkenin
**hiç ortak bir kapsamı** yok — her biri kendi başına atomik, ama ikisi birlikte değil.
`bakiye` okunduğu an tanımlı bir değer taşıyor, `islemSayisi` okunduğu an da tanımlı bir
değer taşıyor; okuma sonucu **yanlış** değil, yalnızca iki okumanın arasına başka bir
goroutine'in bir yazması girebiliyor ve bu, tek başına doğru olan iki değeri bir arada
yanlış bir hikâyeye dönüştürebiliyor. Atomik işlem tek bir değişkeni korumak için var;
birden çok değişkenin **birlikte** tutarlı kalması gerekiyorsa atomik işlem yetmiyor, o
korumayı yeniden bir kilit üstleniyor.

Bu sınırın kendisi bir tasarım hatası değil, önceki dersteki kilit kapsamı örneğinin bir
genellemesi. Orada sorun tek bir sayacın oku-hesapla-yaz üçlüsünün kilidin dışına
taşmasıydı; burada iki ayrı sayacın **ikisi birden** tek bir kapsam altında değil. İkisi
de aynı soruyu soruyor: korunması gereken işlem nerede başlayıp nerede bitiyor? `bakiye` ve
`islemSayisi` ayrı ayrı atomik olduğu için bu soru kaynağa bakılarak fark edilmiyor —
her iki değişken de kendi satırında doğru görünüyor, aralarındaki ilişkinin korunmadığı
yalnızca iki değişkeni **birlikte** okumaya çalışan bir kod parçası yazılıp koşturulduğunda
ortaya çıkıyor.

## Kilit, Bir Kez Çalıştırma, Atomik İşlem

Üç ders boyunca ölçülen üç araç aynı soruya üç ayrı dar yanıt veriyor. Kilit "hangi kod
parçası aynı anda tek bir goroutine tarafından çalışsın?" sorusuna yanıt veriyor ve bu
kapsamı `Lock`/`Unlock` çiftiyle kendi çiziyor — çizim yanlış olursa (birinci dersin
sınırlayıcı ölçümü) kilit bunu fark etmiyor. Bir kez çalıştırma "bu gövde daha önce koştu
mu?" sorusuna yanıt veriyor ve bu soruyu sonsuza dek hatırlıyor; ikinci bir `Do` çağrısı
gövdeyi bir daha hiç çalıştırmıyor, çünkü soru zaten bir kez yanıtlandı. Atomik işlem "bu
tek değişkene aynı anda kaç goroutine dokunabilir?" sorusuna yanıt veriyor ve yanıtı
donanım düzeyinde garanti ediyor, ama soruyu yalnız **tek bir** değişken için soruyor.

Üçü de paylaşılan durumu koruyor, ama üçünün kapsamı birbirinin yerine geçmiyor: bir kez
çalıştırmayla bir sayacı sürekli korumak, ya da atomik işlemle birden fazla adımı tek bir
işlem gibi göstermek mümkün değil. Kod tabanında hangi ilkelin seçildiği, korunması gereken
şeyin doğasını — bir olay mı, tek bir değer mi, yoksa keyfi bir kod parçası mı — açığa
çıkarıyor. Bu üç dersin ortak sonucu bu: paylaşılan durumu korumanın tek bir doğru yolu
yok, korunacak şeyin şekline göre değişen üç ayrı dar sözleşme var.

Bu üç dar sözleşme aynı zamanda birbirine iç içe geçebiliyor. `sync.Once`in kendisi bir
kilitle kuruldu; ikinci dersin `KorumaliSayac`ı gibi bir tip, kendi kilidini taşırken
aynı zamanda bir `sync.Once` alanı taşıyıp yalnız ilk erişimde bir alanı ilklendirebilir.
Atomik işlem de kilidin **yerini almıyor**, kilidin bir yerine geçebileceği en dar
durumları kilitsiz bırakıyor. Üçü de aynı ölçü ekseninin birer görünümü: bir goroutine'in
paylaşılan bir duruma nasıl eriştiği, o durumun ne kadarını, ne kadar süreyle koruma
altında tuttuğuyla ölçülüyor.

## Özet

- `sync.Once`, `Do`ya verilen gövdenin kaç goroutine çağırırsa çağırsın tam olarak bir kez
  koşmasını garanti ediyor; otuz çağrıdan yalnız biri gövdeyi koşturdu.
- Bekleyen goroutine'ler `Once`in gövdesi bitene kadar bloklanıyor — beklediği geldi
  sınıfının kilit ve bekleme grubundan farklı bir düzeneği.
- `sync/atomic`, tek bir değişken üzerindeki işlemleri donanımın bölünmez oku-değiştir-yaz
  desteğiyle koruyor; otuz goroutine on beş bin artış uyguladı ve atomik sayaç kilitli
  sayaçla aynı sonucu (`15000`) verdi.
- İki ayrı atomik değişken, her biri kendi başına tutarlı olsa da, birlikte okunduğunda
  tutarsız bir çift verebiliyor; bu, kanalla zorlanmış bir sırayla modellendi, koşturulmadı.
- Atomik işlem tek bir değişkeni korur; birden fazla değişkenin birlikte tutarlılığı
  gerekiyorsa kilit kapsamı hâlâ gereken araç.
- Üç ders boyunca ölçülen kilit, bekleme grubu, bir kez çalıştırma ve atomik işlem aynı
  ölçü ekseninin dört ayrı görünümü: her biri paylaşılan durumun ne kadarını, ne kadar
  süreyle koruma altında tuttuğuyla tanımlanıyor.

## Sonraki Adım

Üç ders boyunca paylaşılan durum kilitle, bekleme grubuyla ve şimdi bir kez çalıştırma ve
atomik işlemlerle korundu — ama her ölçüm korumanın **var olduğunu** baştan biliyordu,
çünkü kaynağı biz yazdık. Bu üç dersin hiçbirinde gerçek bir yarış koşulu koşturulmadı:
birinci ders bunu ayrık bellekle ve zorlanmış bir modelle, bu ders de zorlanmış bir
modelle dışladı. Gerçek bir kaynak tabanında korumanın unutulup unutulmadığını, kaynağı
okuyarak değil, kim söylüyor? Kursun son dersi bu soruyu, dilin kendi dağıtımıyla gelen
yarış koşulu saptayıcısına soruyor: korumasız bir paylaşım, kilitli bir paylaşım ve kanal
üzerinden bir aktarım aynı saptayıcıdan geçiriliyor, saptayıcının verdiği yanıt (buldu mu,
bulamadı mı) okunuyor, ve saptayıcının kendi sınırı — hiç çalışmayan bir yoldaki yarışı
görememesi — kursun son ölçümü olarak ortaya çıkıyor.
