---
title: 'Bağlam Paketi'
source: 'https://academia.sh/tr/kurslar/go-eszamanlilik/baglam-paketi'
course: Eşzamanlılık
language: tr
updated: '2026-08-23T16:55:14+00:00'
license: 'CC BY-SA 4.0'
---

# Bağlam Paketi

Tek bir iptal, ctx.Done() üzerinde bekleyen dört goroutine'in dördüne de aynı anda ulaşıyor; hepsi 'iptal' sınıfında duruyor ve ctx.Err() aynı sabit sebebi taşıyor. İstek kapsamlı bir değer (context.WithValue) taşımanın ne için olduğu ve ne için olmadığı ayrı ayrı ölçülüyor. Sınırlayıcı ölçüm: bağlam kimseyi zorla durdurmuyor, yalnız haber veriyor — ctx.Done()'u hiç denetlemeyen bir goroutine, bağlam zaten iptal edilmiş olsa bile kendi işini sonuna kadar tamamlıyor.

Önceki ders bir sızıntıyı ölçüp çözümsüz bıraktı: bir boru hattında tüketici erken çıktığında,
önündeki aşamaların bunu bilecek hiçbir yolu yoktu. Aşamaların kendi girdi kanallarını
dinlemesi yetmiyordu — dışarıdan gelen, bütün aşamalara **aynı anda** ulaşan bir sinyale
ihtiyaç vardı. Bu ders o sinyali taşıyan aracı ölçüyor: bağlam paketi (`context` paketi). Bir
bağlamın iptal edilmesi, onu bekleyen kaç goroutine olursa olsun, hepsine tek bir olayla
ulaşıyor — kanaldan farklı olarak burada "kaç alıcı" sorusu hiç yok, çünkü bir bağlamın iptal
sinyali **yayın** gibi çalışıyor, tek bir alıcıya değil hepsine birden.

Bağlam paketinin ortaya çıkardığı `context.Context` tipi kendi başına yeni bir düzenek değil —
altında yatan yalnız bir kanal (`Done()`in döndürdüğü) ve bir hata değeri (`Err()`in
döndürdüğü). Yeni olan, bu ikilinin bir **ağaç** biçiminde türetilebilmesi: bir bağlamdan yeni
bir bağlam türetildiğinde (`context.WithCancel`, `context.WithTimeout` gibi çağrılarla),
üstteki bağlam iptal edildiğinde bütün alt bağlamlar da otomatik olarak iptal ediliyor. Bu ders
tek katmanlı bir ağaçla (`context.Background()`den türeyen tek bir bağlam) başlıyor; ağacın
kendisi bu dersin konusu değil, ama tek bir düğümün iptalinin kaç bekleyene ulaştığı burada net
biçimde ölçülüyor.

## Tek Bir İptal, Zincirdeki Bütün Bekleyenler

Aşağıdaki ölçüm dört goroutine başlatıyor; her biri `ctx.Done()` üzerinde bekliyor. Tek bir
`iptalEt()` çağrısından sonra dördü de raporunu gönderiyor — hepsi aynı sınıfta: **iptal**.

```go
// iptalyayilir.go — tek bir iptal, baglami bekleyen butun goroutine'lere ayni anda ulasir
package main

import (
	"context"
	"fmt"
	"sort"
	"sync"
)

type rapor struct {
	id    int
	sebep string
}

func bekleyen(id int, ctx context.Context, raporlar chan<- rapor, grup *sync.WaitGroup) {
	defer grup.Done()
	<-ctx.Done()
	raporlar <- rapor{id: id, sebep: "iptal"}
}

func main() {
	const bekleyenSayisi = 4
	ctx, iptalEt := context.WithCancel(context.Background())

	raporlar := make(chan rapor, bekleyenSayisi)
	var grup sync.WaitGroup
	for id := 1; id <= bekleyenSayisi; id++ {
		grup.Add(1)
		go bekleyen(id, ctx, raporlar, &grup)
	}

	iptalEt()
	grup.Wait()
	close(raporlar)

	kayitlar := make([]rapor, 0, bekleyenSayisi)
	for r := range raporlar {
		kayitlar = append(kayitlar, r)
	}
	sort.Slice(kayitlar, func(i, j int) bool { return kayitlar[i].id < kayitlar[j].id })

	fmt.Println("-- tek iptal, dort bekleyen --")
	for _, r := range kayitlar {
		fmt.Printf("bekleyen %d  sebep=%s\n", r.id, r.sebep)
	}
	fmt.Println("ctx.Err():", ctx.Err())
}
```

```
-- tek iptal, dort bekleyen --
bekleyen 1  sebep=iptal
bekleyen 2  sebep=iptal
bekleyen 3  sebep=iptal
bekleyen 4  sebep=iptal
ctx.Err(): context canceled
```

**KL15.** `context.WithCancel`in döndürdüğü `ctx.Done()` bir **kanal**, ama sıradan bir kanal
değil: `iptalEt()` çağrıldığında bu kanal **kapanıyor**, ve önceki derslerde defalarca görülen
"kapanan kanaldan alım bloklanmaz" davranışı burada da geçerli. Dört goroutine'in dördü de
`<-ctx.Done()` satırında bekliyordu; kanal kapandığı an dördü de aynı anda serbest kalıyor.
Kapatma önceki derslerde hep **tek bir** göndericiyle (bir üretici, bir aşama) ilişkiliydi;
burada da öyle — `iptalEt`in kendisi tek bir kapatma çağrısını sarmalıyor, birden fazla
`iptalEt` çağrısı bile kanalı yalnız bir kez kapatıyor, önceki dersteki "kanal kapatma atomik
bir işlemdir" gözleminin bağlam paketindeki karşılığı. Bu da bilinçli bir tasarım kararı:
`iptalEt`i birden fazla kez, hatta birden fazla goroutine'den çağırmak güvenli — çağıranın
"acaba daha önce iptal ettim mi" diye kendi durumunu takip etmesi gerekmiyor, çünkü ikinci ve
sonraki çağrılar sessizce hiçbir şey yapmıyor.

Dört raporun `id`ye göre sıralanması (`sort.Slice`), önceki derslerdeki toplama kanallarında
görülen aynı ihtiyaçtan geliyor: dört goroutine `raporlar` kanalına aynı anda yazabiliyor,
kanal kapandığında hangisinin önce ulaştığı zamanlamaya bağlı. Fark, bu kez sıralamanın
**sonucu** etkilememesi — dördünün de sınıfı zaten aynı (iptal), sıralama yalnız çıktının
okunabilir ve tekrarlanabilir olmasını sağlıyor.

**KL16.** Dördünün de aynı sınıfta (**iptal**) durması, önceki derste kurulan dört sınıflı
ölçütün (kendi, beklediği geldi, iptal, hiç durmadı) bu bağlamdaki en yalın hâli: hiçbiri kendi
gövdesini bitirmedi, hiçbiri başka bir goroutine'in bir adımını bekliyordu, dördü de tek bir
iptal olayının doğrudan sonucu olarak durdu. `ctx.Err()`in döndürdüğü metin de sabit —
"context canceled" — dilin kendi standart kütüphanesinin tanımladığı, sürüme bağlı olmayan bir
sabit dize; hangi bağlamın hangi sebeple iptal edildiğini ayırt etmek isteyen bir çağıran bu
metni okuyabiliyor.

Bu dördü, önceki derslerdeki "iptal" sınıfının **tek** örneğiydi (ortak tanımın iki bekleyeni)
— burada aynı sınıf dört kata çıkarılıyor ve sonuç değişmiyor, çünkü kanalın kapanması kaç
alıcı olursa olsun aynı davranışı gösteriyor. Bu ölçek bağımsızlığı bağlam paketinin asıl
gücü: bir web sunucusunda tek bir isteğin iptali, o isteği işleyen onlarca goroutine'e (bir
veritabanı sorgusu, birkaç dış çağrı, bir önbellek okuması) aynı `ctx.Done()` kanalı
üzerinden, hiçbirini tek tek uyarmaya gerek kalmadan ulaşabiliyor.

## İstek Kapsamlı Değer Taşımak

Bağlam paketinin ikinci işi iptal yaymak değil, bir **istek kapsamlı değeri** çağrı zinciri
boyunca taşımak — örneğin bir istek kimliğini, zincirdeki her işlevin imzasını değiştirmeden,
en derindeki çağrıya kadar ulaştırmak. Bu, önceki derslerdeki hiçbir kanal düzeneğiyle
karşılanmayan ayrı bir ihtiyaç: kanallar veri **akıtır**, bağlam değerleri ise çağrı zincirinin
**yanında** taşır. İşçi havuzu, yayma/toplama ve boru hattı ölçümlerinin üçünde de veri hep
açık bir parametre ya da açık bir kanal üzerinden aktı; `context.WithValue`in çözdüğü sorun
farklı — bir istek kimliğini, o isteği işleyen zincirdeki her işlevin imzasına ayrı ayrı
eklemeden, zincirin en derinine kadar ulaştırmak.

Değer taşıma, iptal yaymadan bağımsız çalışıyor: aşağıdaki ölçüm önce bir değer taşıma
kurulumunu, sonra (aynı programda, ayrı bir bağlamla) iptalin kimseyi durdurmadığını gösteren
sınırlayıcı ölçümü kuruyor.

```go
// degerVeIhmal.go — istek kapsamli deger tasima ve iptali hic denetlemeyen goroutine
package main

import (
	"context"
	"fmt"
)

type anahtarTipi string

const istekAnahtari anahtarTipi = "istek-kimligi"

func iclerdeOku(ctx context.Context) string {
	deger, varMi := ctx.Value(istekAnahtari).(string)
	if !varMi {
		return "yok"
	}
	return deger
}

func main() {
	ctx := context.WithValue(context.Background(), istekAnahtari, "istek-42")
	fmt.Println("iclerdeki islevin okudugu istek kimligi:", iclerdeOku(ctx))

	ctx2, iptalEt := context.WithCancel(context.Background())
	iptalEt() // bu goroutine baslamadan once iptal zaten gerceklesti

	adimlar := make(chan int, 3)
	go func() {
		// ctx2.Done() hic denetlenmiyor; bu goroutine iptali hic bilmiyor.
		for adim := 1; adim <= 3; adim++ {
			adimlar <- adim
		}
		close(adimlar)
	}()

	tamamlananAdim := 0
	for range adimlar {
		tamamlananAdim++
	}
	fmt.Println("goroutine calisirken baglam zaten iptal edilmis miydi:", ctx2.Err() != nil)
	fmt.Printf("tamamlanan adim sayisi: %d / 3\n", tamamlananAdim)
}
```

```
iclerdeki islevin okudugu istek kimligi: istek-42
goroutine calisirken baglam zaten iptal edilmis miydi: true
tamamlanan adim sayisi: 3 / 3
```

**KL17.** `istekAnahtari`, sıradan bir dizgi değil, kendi tanımlı `anahtarTipi` türünde bir
sabit — bu, bir başka paketin aynı dizgi değerini kendi anahtarı olarak kullanıp yanlışlıkla
aynı bağlam yuvasına yazmasını derleme zamanında engelliyor. `iclerdeOku`, `ctx.Value`in
döndürdüğü değeri **tip iddiasıyla** (`.(string)`) okuyor ve ikinci dönüş değeriyle (`varMi`)
değerin hiç bulunmama ihtimalini ayırıyor — sözlükteki iki yanıtlı erişimin (**M09/K01**'den)
bağlam değerlerindeki karşılığı.

Bu taşıma biçiminin **ne olmadığı** da ölçünün bir parçası: `context.WithValue`, bir işlevin
ihtiyaç duyduğu **gerekli** bir bağımlılığı ya da parametreyi taşımak için değil, bir çağrı
zincirinin **her** katmanından geçmesi gereken, isteğe özgü ve o katmanların çoğunun
kullanmadığı üstbilgi türünden veriler (bir istek kimliği, bir izleme belirteci) için var.
`iclerdeOku` işlevi, istek kimliğini bulamazsa (`varMi` `false` ise) çökmüyor, yalnız "yok"
döndürüyor — çünkü bağlam değeri **isteğe bağlı** kabul ediliyor. Bir işlevin çalışması için
zorunlu olan bir değer (bir veritabanı bağlantısı, bir yapılandırma) bağlam üzerinden değil,
doğrudan bir parametre olarak geçirilmeli; aksi hâlde işlevin imzası neye ihtiyaç duyduğunu
söylemekten vazgeçmiş olur.

Bu ayrımın pratik sonucu, `iclerdeOku` gibi bir işlevin imzasına bakarak görülemiyor:
`func iclerdeOku(ctx context.Context) string` imzası, işlevin bir istek kimliğine ihtiyaç
duyduğunu hiç söylemiyor — yalnız bir bağlam alıyor, ve bağlamın içinde ne olduğu çağrı
zamanına kadar belirsiz kalıyor. Bu, önceki dersteki `nil` sözlük ve `nil` işaretçi
davranışlarına benzer bir açıklık: tip sistemi, bağlamın **hangi** anahtarları taşıdığını
denetlemiyor, yalnız bir anahtar sorulduğunda `nil`/`varMi=false` ile yokluğu bildiriyor.
Bir işlevin hangi bağlam anahtarlarını okuduğunu bilmenin tek yolu kaynağı okumak — imza bu
konuda hiçbir söz vermiyor.

## Bağlam Kimseyi Durdurmaz, Yalnız Haber Verir

İkinci ölçüm bunu doğrudan kanıtlıyor. `ctx2` goroutine başlamadan **önce** zaten iptal edilmiş
— `iptalEt()` çağrısı, `go func() {...}()` satırından önce çalışıyor. Bu sıralama bilinçli:
ölçümün amacı, "iptal henüz gerçekleşmemişken goroutine başladı, sonra iptal yetişemedi" gibi
bir zamanlama tartışmasını tamamen ortadan kaldırmak. Bağlam, goroutine daha var olmadan
zaten iptal edilmiş durumda; buna rağmen başlatılan goroutine `ctx2.Done()`u hiç denetlemiyor,
yalnız üç adımı sırayla üretip kanalını kapatıyor.

**KL18.** Sonuç net: `ctx2.Err() != nil` `true` — goroutine çalışmaya başladığında bağlam
**zaten** iptal edilmişti — ama `tamamlanan adim sayisi` yine de `3 / 3`. Goroutine, bağlamın
iptal edilip edilmediğine hiç bakmadığı için, iptalin kendisinden **tamamen habersiz**
çalışmaya devam etti ve işini eksiksiz bitirdi. Bu, önceki dersin kapanışında bırakılan sorunun
tam yanıtı: bağlam paketi kimseyi zorla durdurmuyor, bir goroutine'i **kesip atan** bir
mekanizma değil. Yaptığı tek şey, iptal olduğunda bunu isteyen herkese haber vermek —
`ctx.Done()`u dinleyenler bu haberi alıp kendi kararlarını veriyor, dinlemeyenler hiçbir şey
fark etmiyor.

Bu davranış ilk bakışta bir kusur gibi görünebilir — neden bağlam kendisi bir goroutine'i
durdurmuyor? Yanıt Go'nun genel tasarımında yatıyor: dilin çalışma zamanı, bir goroutine'i
dışarıdan **zorla** durdurma aracı hiç sunmuyor (bir iş parçacığını dışarıdan kesmeye benzer
bir çağrı Go'da yok). Bir goroutine yalnız kendi isteğiyle, kendi kod yolunda bir `return`e ya
da fonksiyonun sonuna ulaşarak durabiliyor. Bağlam paketi bu kısıtın içinde çalışıyor: zorla
durdurma sunmadığı için, elindeki tek araç **bilgilendirme** — durması gerektiğini düşünen
goroutine'e bunu söylemek, kararı ona bırakmak. Bu, önceki derslerde defalarca görülen "durma
kararı her zaman goroutine'in kendi gövdesinde bir kontrol noktasına bağlıdır" ilkesinin en
açık örneği: kontrol noktası burada `select` içindeki bir `case <-ctx.Done()` dalı, ve o dal
kaynakta hiç yazılmamışsa iptalin kendisi de bir anlam ifade etmiyor.

Bu, önceki derste ölçülen boru hattı sızıntısının çözümünün de sınırını çiziyor: bir aşamaya
`ctx.Done()`u denetleyen bir `select` dalı eklemek, o aşamayı **iptali fark eden** hâle
getiriyor — ama bu ekleme her aşamada ayrı ayrı yapılmak zorunda. Örneğin `kareAl` aşaması,
kendi çıktısına düz bir gönderim yerine iki dallı bir `select` yazsaydı — biri değeri gönderen,
öbürü `ctx.Done()`u bekleyen — tüketici erken çıktığında bağlamı iptal ederek bu aşamayı da
serbest bırakabilirdi. Ama bu değişikliği zincirdeki **her** aşama ayrı ayrı yapmak zorunda;
bağlamın kendisi hiçbir goroutine'i otomatik olarak durdurmuyor, yayılan şey yalnız **bilgi**,
bu bilgiyi eyleme çevirmek her goroutine'in kendi sorumluluğunda kalıyor.

İptal bağlamının bu doğası, bir `context.Context` değerinin neden bir kilit ya da bir kesme
(interrupt) gibi çalışmadığını açıklıyor: o yalnız kapanan bir kanal, ve kapanan bir kanalı
kim dinliyorsa yalnız o tepki veriyor. Bu da bağlam paketini önceki derslerdeki bütün
düzeneklerle aynı temele oturtuyor — kanal, bekleme grubu, `select`, hepsi aynı ilkeye
dayanıyor: eşzamanlılık Go'da bir goroutine'i dışarıdan yönetmekle değil, goroutine'lerin
birbirine **haber verdiği** bir kanal ağıyla kuruluyor.

## Özet

- Bir bağlamın iptal edilmesi, `ctx.Done()` üzerinde bekleyen kaç goroutine olursa olsun,
  hepsine aynı anda ulaşıyor; hepsi aynı sınıfta (iptal) duruyor.
- `ctx.Err()`, dilin standart kütüphanesinin tanımladığı sabit bir metin döndürüyor; hangi
  bağlamın iptal edildiğini ayırt etmek için okunabiliyor.
- `context.WithValue`, isteğe bağlı, katmanlar arasında taşınan üstbilgi türünden veriler
  içindir; bir işlevin zorunlu bağımlılıklarının yerini tutmaz.
- Bağlam kimseyi zorla durdurmuyor, yalnız haber veriyor — `ctx.Done()`u denetlemeyen bir
  goroutine, bağlam iptal edilmiş olsa bile işini sonuna kadar sürdürüyor.
- Bir goroutine'i iptale duyarlı hâle getirmek, o goroutine'in kendi döngüsüne bir `ctx.Done()`
  denetimi eklemesini gerektiriyor; bu otomatik gerçekleşmiyor.
- Go'nun çalışma zamanı bir goroutine'i dışarıdan zorla durdurma aracı sunmuyor; bağlam bu
  kısıt içinde çalışıyor ve elindeki tek araç bilgilendirme, zorlama değil.

## Sonraki Adım

Bu derste bağlamın **kendisinin** ne zaman iptal edildiği önceden biliniyordu — `iptalEt()`
açıkça çağrıldı, bekleyenler bu çağrıyı bekledi. Ama bir bağlamın iptal edilmesinin en yaygın
sebebi, birinin bilinçli bir karar vermesi değil, **bir sürenin dolması**: bir ağ çağrısının,
bir veritabanı sorgusunun makul bir süre içinde yanıt vermesini beklemek, vermezse vazgeçmek.
Sıradaki ders bu özel durumu ölçüyor: bir `select` deyimi bir sonucu mu yoksa bir iptali mi
aldığına göre bir dal seçtiğinde, zaman aşımı da bu seçimin bir biçimi oluyor — ölçülen şey
geçen süre değil, hangi dalın seçildiği; bu ders zaten bilinen bir iptali ölçtü, sıradaki ders
**henüz gerçekleşmemiş** bir iptali (bir zaman aşımını) aynı `select` düzeneğiyle ölçüyor.
