---
title: 'Zaman Aşımı ve Son Tarihler'
source: 'https://academia.sh/tr/kurslar/go-eszamanlilik/zaman-asimi-ve-son-tarihler'
course: Eşzamanlılık
language: tr
updated: '2026-08-23T16:55:14+00:00'
license: 'CC BY-SA 4.0'
---

# Zaman Aşımı ve Son Tarihler

Zaman aşımı bir select deyiminin bir dal seçimidir: sonuç önceden hazırsa sonuç dalı, bağlam önceden iptal edilmişse (zaman aşımını temsilen) iptal dalı seçiliyor — ikisi de belirlenimci, çünkü kurulumda yalnız bir dal gerçekten hazır. Süre hiçbir yerde ölçülmüyor, basılan tek şey hangi dalın seçildiği. Sınırlayıcı ölçüm: zaman aşımı bekleyen tarafı kurtarıyor ama beklenen işi durdurmuyor — dalından çıkan çağıranın arkasında, sonucunu artık kimsenin almayacağı bir iş goroutine'i sızıntı olarak kalıyor.

Önceki ders bir bağlamın iptalinin, o iptal **zaten gerçekleştikten sonra** bekleyenlere nasıl
ulaştığını ölçtü — `iptalEt()` açıkça çağrılmıştı, bekleyenler bu çağrıyı bekliyordu. Ama
gerçek sistemlerde iptalin en sık sebebi bir kişinin kararı değil, bir **sürenin dolması**: bir
ağ çağrısının makul bir sürede yanıt vermesini beklemek, vermezse vazgeçmek. Bu ders zaman
aşımını ölçüyor — ama süre olarak değil. Zaman aşımı, aslında bir `select` deyiminin iki
dalından birinin seçilmesinden ibaret: sonuç mu geldi, iptal mi geldi. Bu ders süreyi hiç
ölçmeden, hangi dalın seçildiğini **belirlenimci** biçimde kuruyor.

Bağlam paketi bunun için iki ayrı kurucu sunuyor: `context.WithTimeout`, şu andan itibaren
**göreli** bir süre sonra iptal eden bir bağlam kurar; `context.WithDeadline` ise takvimde
**mutlak** bir an (bir son tarih) belirtir ve o ana ulaşıldığında iptal eder. İkisi de aynı
sonuca varıyor — belirli bir noktada kendiliğinden kapanan bir `Done()` kanalı — ve `select`
açısından ikisi de önceki dersteki `context.WithCancel`den hiçbir şekilde ayırt edilemiyor.
Bu ders bu yüzden ikisini de ayrı ayrı kurmuyor; ikisinin de vardığı ortak nokta olan "bağlam
bir noktada kapanmış" durumunu modelliyor ve gerçek bir süre ya da gerçek bir takvim değeri
hiç yazdırmıyor.

## Bir Dal Seçimi: Sonuç mu, İptal mi

Aşağıdaki ölçüm iki kurulumu yan yana koyuyor. Birincisinde sonuç **önceden hazır**: `select`
çalıştığında sonuç kanalında zaten bir değer bekliyor, bağlam iptal edilmemiş. İkincisinde
bağlam **önceden iptal edilmiş** — zaman aşımını temsilen — ve sonuç kanalı boş, hiçbir
gönderici yok. Her iki kurulumda da `select`in hangi dalı seçeceği önceden bellidir, çünkü
kurulumun kendisinde yalnız **bir** dal gerçekten hazır.

```go
// dalSecimi.go — zaman asimi bir dal secimidir; sure degil hangi dalin secildigi olculur
package main

import (
	"context"
	"fmt"
)

func dalSec(ctx context.Context, sonuc <-chan string) string {
	select {
	case deger := <-sonuc:
		return "sonuc: " + deger
	case <-ctx.Done():
		return "iptal: " + ctx.Err().Error()
	}
}

func main() {
	fmt.Println("-- kurulum 1: sonuc onceden hazir --")
	ctxHazir, iptalHazir := context.WithCancel(context.Background())
	defer iptalHazir()
	sonucHazir := make(chan string, 1)
	sonucHazir <- "tamamlandi"
	fmt.Println("secilen dal:", dalSec(ctxHazir, sonucHazir))

	fmt.Println()
	fmt.Println("-- kurulum 2: baglam onceden iptal edilmis (zaman asimini temsil eder) --")
	ctxIptal, iptalEt := context.WithCancel(context.Background())
	iptalEt()
	sonucBos := make(chan string)
	fmt.Println("secilen dal:", dalSec(ctxIptal, sonucBos))
}
```

```
-- kurulum 1: sonuc onceden hazir --
secilen dal: sonuc: tamamlandi

-- kurulum 2: baglam onceden iptal edilmis (zaman asimini temsil eder) --
secilen dal: iptal: context canceled
```

**KL19.** Her iki kurulumda da `dalSec` çağrıldığı anda `select`in önünde yalnız **bir** hazır
dal var — bu, sonucun rastgele seçilmediğinin garantisi. Kurulum 1'de `sonucHazir` tamponlu
bir kanal ve içine değer `select` çalışmadan **önce** konmuş; `ctxHazir` iptal edilmemiş,
yani `ctx.Done()` hiç kapanmamış. `select`, iki daldan yalnız birinin hazır olduğu bir durumda
her zaman o dalı seçiyor — burada seçim şansa değil, kurulumun kendisine bağlı. Kurulum 2'nin
aynası: `ctxIptal` `dalSec` çağrılmadan önce iptal edilmiş, `sonucBos` ise tamamen boş ve
hiçbir goroutine ona yazmıyor — `ctx.Done()` dalı burada tek hazır dal.

Bu iki kurulum bilerek uç noktalara yerleştirildi: gerçek bir yarışta iki dal da **aynı anda**
hazır hâle gelebilir (sonuç tam zaman aşımıyla aynı ana denk gelirse), ve o durumda `select`
hangisini seçeceğine rastgele karar veriyor — bu, önceki derste kanal seçiminde görülen aynı
rastgelelik kuralı. Ama bu ders o ortak noktayı hiç kurmuyor: iki kurulumun ikisi de yalnız
bir dalın hazır olduğu, öbürünün kesinlikle hazır olmadığı durumlar. Belirlenimcilik buradan
geliyor — ölçüm rastgeleliğin **olmadığı** iki köşeyi seçiyor, rastgeleliğin olabileceği orta
bölgeyi hiç koşturmuyor.

**KL20.** Gerçek bir zaman aşımı `context.WithTimeout` ile kurulur ve bağlam belirli bir süre
sonra **kendiliğinden** iptal olur — bu dersin ölçtüğü kurulumlar o gerçek zamanlamayı
**modelliyor**, gerçek bir süre geçmesini hiç beklemeden. Kurulum 2'deki `ctxIptal`in "zaten
iptal edilmiş" olması, "süresi dolmuş bir zaman aşımı bağlamı" ile aynı sonucu üretiyor:
`select`in görünen yüzü ikisinde de aynı, çünkü `select` bir bağlamın **neden** iptal
edildiğini bilmiyor, yalnız `Done()` kanalının kapalı olup olmadığını görüyor. Bu, ölçümün
gerçek bir zaman aşımı kadar geçerli olmasını sağlıyor — fark yalnız iptalin **ne zaman**
tetiklendiğinde, `select`in davranışında değil.

Süre burada hiçbir satırda geçmiyor: ne bir `time.Sleep`, ne bir `time.After`, ne bir ölçülen
gecikme. Ölçülen tek şey **hangi dalın seçildiği** — bu, önceki dersteki "süre değil, seçilen
dal yazılır" kuralının doğrudan uygulanışı. `time.Sleep` ile bir sıra kurmanın neden yasak
olduğu burada bir kez daha görünüyor: eğer bu ölçüm gerçek bir zaman aşımı süresiyle
kurulsaydı, hangi dalın kazanacağı makinenin o anki yüküne bağlı kalırdı — modelleme bu
belirsizliği tamamen ortadan kaldırıyor.

`dalSec` işlevinin kendisi hem kurulum 1'de hem kurulum 2'de **aynı** — hiçbir dallanma,
hiçbir özel durum kodu yok. Bu, zaman aşımı düzeninin en pratik yanı: bir işlevi zaman aşımına
duyarlı yapmak, o işlevin içine "eğer süre dolduysa" türünden bir kontrol eklemeyi
gerektirmiyor; yalnız bir `select` içine iptal dalını eklemek yetiyor, gerisini kapatma
sorumluluğu zaten kurulmuş olan bağlam kendisi hallediyor. Aynı `dalSec`, çağırana
`WithCancel`den gelen bir iptal ile de, `WithTimeout`dan gelen bir zaman aşımıyla da, hiçbir
değişiklik gerekmeden hizmet ediyor.

## Zaman Aşımı Bekleyeni Kurtarır, Beklenen İşi Durdurmaz

Yukarıdaki `dalSec` çağrısı döndüğünde, çağıran taraf **kurtulmuş** oluyor — artık bekleme
yok, bir sonuç ya da bir iptal metniyle devam edebiliyor. Ama bu kurtuluş, sonucu üretmesi
beklenen işin de durduğu anlamına gelmiyor. Aşağıdaki ölçüm bunu kurulum 2'nin üzerine inşa
ediyor: çağıran taraf iptal dalından çıktıktan **sonra**, aslında sonucu üretmekte olan bir iş
goroutine'i başlıyor ve sonucunu göndermeye çalışıyor — ama artık onu alacak kimse yok.

```go
// zamanasimiSizinti.go — zaman asimi bekleyeni kurtarir ama beklenen isi durdurmaz
package main

import (
	"context"
	"fmt"
	"runtime"
)

func dalSec(ctx context.Context, sonuc <-chan string) string {
	select {
	case deger := <-sonuc:
		return "sonuc: " + deger
	case <-ctx.Done():
		return "iptal: " + ctx.Err().Error()
	}
}

func main() {
	ctx, iptalEt := context.WithCancel(context.Background())
	iptalEt() // zaman asimini temsil eder: cagiran zaten vazgecmis durumda

	sonuc := make(chan string) // tamponsuz, henuz hicbir gonderici yok

	fmt.Println("-- cagiran dalindan cikiyor --")
	fmt.Println("secilen dal:", dalSec(ctx, sonuc))

	// Cagiran cikti. Is aslinda hala suruyormus gibi modellemek icin is goroutine'i
	// simdi baslatiliyor ve sonucunu ayni kanala yazmaya calisiyor. Baglami hic
	// denetlemedigi icin devam ediyor, ama artik alici yok.
	taban := runtime.NumGoroutine()
	go func() {
		sonuc <- "gec kalan sonuc"
	}()

	fmt.Printf("is goroutine'i hala calisiyor mu (sizinti): %v\n", runtime.NumGoroutine() > taban)
}
```

```
-- cagiran dalindan cikiyor --
secilen dal: iptal: context canceled
is goroutine'i hala calisiyor mu (sizinti): true
```

**KL21.** `dalSec` çağrıldığında `sonuc` kanalına henüz hiçbir goroutine yazmıyor — bu yüzden
seçilen dal kesin biçimde `ctx.Done()`. İş goroutine'i ancak `dalSec` döndükten **sonra**
başlıyor; bu sıralama bilinçli, çünkü amaç iş goroutine'inin `select` sırasında var olup
olmamasını bir zamanlama sorusu olmaktan çıkarmak. İş goroutine'i başladığında kendi sonucunu
`sonuc` kanalına göndermeye çalışıyor, ama kanal tamponsuz ve artık onu okuyan hiç kimse yok —
`dalSec` çoktan döndü, ana goroutine artık `sonuc`u dinlemiyor. Gönderim kalıcı olarak
bloke oluyor.

**KL22.** `runtime.NumGoroutine() > taban` sonucu `true` — bu, önceki derslerde defalarca
görülen boolean sızıntı ölçütünün burada üçüncü kez, ama yeni bir sebeple ortaya çıkışı: işçi
havuzunda sebep kapatmanın unutulmasıydı, yaymada sorumluluğun hiç verilmemesiydi, boru
hattında işin hiç bitmemesiydi. Burada sebep tamamen farklı — kapatılacak bir kanal bile yok,
sorun iş goroutine'inin **kimin dinlediğini hiç bilmemesi**. `dalSec`in `ctx.Done()` dalını
seçmesi, iş goroutine'ine hiçbir şey söylemiyor; iş goroutine'i kendi payına düşen hesaplamayı
yapıp sonucu göndermeye çalışmaya devam ediyor, tıpkı önceki dersteki ihmalkâr goroutine'in
iptali hiç denetlemeden işini bitirmesi gibi.

`taban`ın bu ölçümde iş goroutine'i başlamadan **hemen önce**, ikinci bir kez yakalanması da
dikkat çekici: ilk iki kurulumun kendi goroutine'leri (varsa) zaten sonlanmış olabilir, ölçümün
yalnız üçüncü kurulumdaki tek goroutine'e odaklanması gerekiyor. Bu, önceki derslerdeki
`runtime.NumGoroutine()` kullanımlarıyla aynı disiplin: taban her zaman ölçülecek goroutine'in
başlatılmasından hemen önce alınıyor, böylece karşılaştırma yalnız o tek goroutine'in var olup
olmadığını yansıtıyor — programın başından beri çalışan başka goroutine'lerin sayısı
karışmıyor.

Bu, dersin başlığındaki iki sözcüğün ("zaman aşımı" ve "kaynak sızıntısı") neden birlikte
anıldığını açıklıyor. Zaman aşımı, çağıranı bekleme durumundan **kurtarıyor** — çağıran artık
sonsuza kadar bloke değil, bir yanıtla (iptal ya da sonuç) devam edebiliyor. Ama zaman aşımının
kurtardığı yalnız **bekleyen taraf**; sonucu üretmesi beklenen iş, zaman aşımından tamamen
habersiz, kendi hızında çalışmaya devam ediyor. Çağıran "vazgeçtim" dediğinde iş goroutine'ine
bu haber hiç ulaşmıyor — tıpkı önceki dersteki gibi, bağlam kimseyi zorla durdurmuyor, yalnız
bekleyen tarafa bir çıkış kapısı açıyor.

Bu ölçümdeki iş goroutine'i kasıtlı olarak en yalın hâliyle kuruldu: tek bir gönderim, hiçbir
hesaplama yok. Gerçek bir sistemde bu goroutine bir ağ isteği, bir veritabanı sorgusu ya da bir
dosya okuması olurdu — kendi süresi olan, kesilmeden tamamlanmak isteyen bir iş. Zaman aşımı
o işin kendisini durdurmuyor; yalnız **çağıranın** o işi beklemekten vazgeçmesini sağlıyor.
İşin arkada gerçekten tamamlanıp tamamlanmadığı bu ölçümün konusu değil — burada kesin olan
tek şey, işin sonucunu **artık kimsenin almayacağı**, çünkü tamponsuz kanalın tek okuyucusu
zaten ayrılmış durumda.

Bunun pratik sonucu açık: bir zaman aşımı kurmak tek başına yeterli değil. İş goroutine'inin
kendisi de aynı bağlamı **paylaşmalı** ve kendi `ctx.Done()`unu denetlemeli — aksi hâlde
çağıran taraf "zaman aşımına uğradı" deyip devam ederken, arkada bırakılan iş sonsuza kadar
sürebiliyor. Bu ders bu ikinci adımı **kurmuyor**, yalnız kurulmadığında ne olduğunu ölçüyor;
iş goroutine'inin de aynı `ctx`i alıp kendi `select`inde denetlemesi, önceki iki derste
görülen düzeneğin (bir aşamaya `ctx.Done()` dalı eklemek) doğrudan tekrarı olurdu.

Bu payın beş dersi, aynı sorunun beş ayrı yüzünü ölçtü ve hepsi aynı noktada buluşuyor: bir
goroutine, kendi `ctx.Done()`unu ya da kendi kapatılan kanalını **denetlemediği** sürece, ne
kadar iyi niyetli bir düzenek kurulursa kurulsun, dışarıdan gelen hiçbir sinyal onu durduramaz.
İşçi havuzunda bu denetim `for is := range isler`in kendisiydi; boru hattında her aşamanın
kendi girdi kanalıydı; burada ise iş goroutine'inin hiç yazmadığı, eksik kalan bir `select`
dalı. Beşi de aynı dersi farklı bir açıdan tekrar ediyor: durma, goroutine'in kendi kod
yolunda bir kontrol noktasına uğramasına bağlı, kontrol noktası yoksa dışarıdaki hiçbir sinyal
bir anlam taşımıyor.

## Özet

- Zaman aşımı bir `select` deyiminin bir dal seçimidir; süre hiç ölçülmez, yalnız hangi dalın
  seçildiği yazılır.
- Sonuç önceden hazırsa ya da bağlam önceden iptal edilmişse, `select`in seçtiği dal
  belirlenimcidir — kurulumda yalnız bir dal gerçekten hazır olduğu için.
- İptal edilmiş bir bağlamın `select` açısından görünümü, süresi dolmuş bir zaman aşımı
  bağlamıyla aynıdır; `select` iptalin sebebini bilmez, yalnız `Done()`in kapalı olup
  olmadığını görür.
- Zaman aşımı bekleyen tarafı kurtarır ama sonucu üretmesi beklenen işi durdurmaz; iş
  goroutine'i kendi bağlamını denetlemediği sürece çalışmaya devam eder.
- Bu son sızıntı da bir boolean olarak ölçülür; sebebi önceki derslerdeki üç sebepten
  (unutulmuş kapatma, sahipsiz sorumluluk, bitmeyen iş) farklı — kimin dinlediğini bilmeyen
  bir gönderici.
- `context.WithTimeout` (göreli süre) ve `context.WithDeadline` (mutlak an), `select`
  açısından `context.WithCancel`den ayırt edilemez; üçü de kapanan bir `Done()` kanalı verir.

## Sonraki Adım

Bu payın beş dersi boyunca kurulan her düzenek — işçi havuzu, yayma ve toplama, boru hattı,
bağlam, zaman aşımı — aynı ilkeye dayandı: goroutine'ler birbirine **iletişimle** haber
veriyordu, bir kanal göndererek ya da bir kanalı kapatarak. Zaman aşımı bekleyeni kurtarır ama
beklenen işi durdurmaz; sıradaki konu paylaşılan duruma dönüyor — iletişim yerine ortak bir
değeri korumayı seçtiğinde ne değişiyor. Orada goroutine'ler artık birbirine kanal üzerinden
haber vermiyor, aynı belleğe erişimlerini bir kilitle sınırlıyor — ve durma kararı, kilidi
bırakan tarafın bir adımına bağlı kalmaya devam ediyor.
