Go'nun context paketi, son tarihleri (deadline), iptal sinyallerini ve isteğe özgü değerleri API sınırları boyunca ve goroutine'ler arasında taşıyan context.Context tipini tanımlar. Bir işi iptal edilebilir yapmak için ctx, cancel := context.WithCancel(ebeveyn), süre sınırı koymak için context.WithTimeout(ebeveyn, süre) kullanırsınız ve iş bittiğinde mutlaka cancel() çağırırsınız. İşi yapan kod ctx.Done() kanalını dinler; kanal kapandığında işi bırakır ve ctx.Err() ile nedenini döndürür. Bir context iptal edildiğinde ondan türetilen bütün context'ler de iptal edilir.
Bu yazıda Context arayüzünün dört metodunu, WithCancel, WithTimeout, WithDeadline ve WithValue ile türetilen context ağacını, Go 1.20 ve 1.21 ile gelen WithCancelCause, Cause, AfterFunc ve WithoutCancel'ı, paket belgesindeki kullanım kurallarını ve go vet'in yakaladığı iptal sızıntısını Go 1.27.3 ile çalıştırılmış örneklerle anlatıyoruz. Goroutine ve kanal temelleri için goroutine ve select yazılarına, seri için Go öğrenme rehberi sayfasına bakabilirsiniz.
Context neden var?

Go Blog'daki Context yazısı ihtiyacı sunucu örneğiyle açıklar: Go sunucularında her gelen istek kendi goroutine'inde işlenir ve istek işleyicileri veritabanı ya da başka servislerle konuşmak için ek goroutine'ler başlatır. Bu goroutine'lerin kullanıcının kimliği, yetki belirteci ve isteğin son tarihi gibi isteğe özgü bilgilere erişmesi gerekir. İstek iptal edildiğinde ya da süresi dolduğunda, o istek için çalışan bütün goroutine'lerin hızla çıkması ve kaynakları bırakması gerekir. context paketi bu üç ihtiyacı, yani iptal, son tarih ve isteğe özgü değerleri, tek bir değerde toplar.
Context bir arayüzdür ve dört metodu vardır:
| Metot | Döndürdüğü | Anlamı |
|---|---|---|
| Deadline() | (time.Time, bool) | İşin iptal edileceği zaman; son tarih yoksa ok false |
| Done() | <-chan struct{} | İş bırakılmalıyken kapanan kanal; hiç iptal edilemeyen context için nil olabilir |
| Err() | error | Done kapanmadıysa nil; kapandıysa Canceled ya da DeadlineExceeded |
| Value(key) | any | Anahtara bağlı değer; yoksa nil |
Arayüzde bir Cancel metodu bulunmaz. Blog yazısı bunun nedenini Done kanalının yalnızca alınabilir olmasıyla aynı mantığa bağlar: iptal sinyalini alan fonksiyon genellikle sinyali gönderen değildir. Bir işlem alt işlemler için goroutine başlattığında alt işlemler ebeveyni iptal edememelidir. İptal yetkisi, context'i türeten fonksiyonun döndürdüğü cancel fonksiyonundadır. Aynı context istenen sayıda goroutine'e verilebilir; context'ler eşzamanlı kullanım için güvenlidir.
Background ve TODO
Her context ağacının bir kökü vardır. context.Background() boş, nil olmayan, hiç iptal edilmeyen, değeri ve son tarihi olmayan bir context döndürür; genellikle main fonksiyonunda, başlatma kodunda, testlerde ve gelen isteklerin en üst context'i olarak kullanılır. context.TODO() da aynı şekilde boştur, ancak hangi context'in kullanılacağının belli olmadığı ya da çevredeki fonksiyonun henüz context parametresi almadığı yerlerde işaret olarak kullanılır. Paket belgesi, bir fonksiyon izin verse bile nil context geçirmemenizi, emin değilseniz TODO kullanmanızı ister.
WithCancel ile iptal
WithCancel(ebeveyn), ebeveyni gösteren ama yeni bir Done kanalına sahip türetilmiş bir context ve bir cancel fonksiyonu döndürür. Türetilmiş context'in Done kanalı, cancel çağrıldığında ya da ebeveynin Done kanalı kapandığında, hangisi önce olursa, kapanır. Paket belgesindeki örneğin uyarlaması, sonsuz sayı üreten bir goroutine'in tüketici işini bitirince nasıl durdurulduğunu gösterir:
package main
import (
"context"
"fmt"
)
// gen, ctx iptal edilene kadar sayı üretir.
func gen(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
return // iptal geldi: goroutine sızmadan çıkar
case dst <- n:
n++
}
}
}()
return dst
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // işimiz bitince üreticiyi durdur
for n := range gen(ctx) {
fmt.Println(n)
if n == 3 {
break
}
}
}
// Çıktı:
// 1
// 2
// 3defer cancel() olmasaydı, döngüden break ile çıktıktan sonra üretici goroutine bir sonraki sayıyı göndermek için sonsuza kadar bekler ve sızardı. Üreticinin göndermeyi ctx.Done() ile aynı select içinde yapması, iptali fark edebilmesinin tek yoludur. CancelFunc işin durmasını beklemez; yalnızca sinyali verir. Birden çok goroutine aynı anda çağırabilir ve ilk çağrıdan sonraki çağrılar hiçbir şey yapmaz.
İptalin yayılması: context ağacı
Türetilen context'ler bir ağaç oluşturur. Bir context iptal edildiğinde ondan türetilen bütün context'ler de iptal edilir; ancak çocuğun iptal edilmesi ebeveyni etkilemez. Aşağıdaki programda bir ebeveyn ve iki çocuk var:
package main
import (
"context"
"fmt"
)
func main() {
kok := context.Background()
ebeveyn, ebeveynIptal := context.WithCancel(kok)
cocuk1, cocuk1Iptal := context.WithCancel(ebeveyn)
cocuk2, cocuk2Iptal := context.WithCancel(ebeveyn)
defer cocuk2Iptal()
cocuk1Iptal() // yalnızca cocuk1 ve onun altı iptal olur
<-cocuk1.Done()
fmt.Println("cocuk1:", cocuk1.Err())
fmt.Println("ebeveyn:", ebeveyn.Err())
fmt.Println("cocuk2:", cocuk2.Err())
ebeveynIptal() // ebeveyn iptal: tüm çocuklar da iptal
<-cocuk2.Done()
fmt.Println("cocuk2:", cocuk2.Err())
fmt.Println("Background Done nil mi:", kok.Done() == nil)
}
// Çıktı:
// cocuk1: context canceled
// ebeveyn: <nil>
// cocuk2: <nil>
// cocuk2: context canceled
// Background Done nil mi: trueÖrnekte Err'i okumadan önce Done kanalını bekliyoruz, çünkü arayüz belgesi Done kanalının kapanmasının cancel fonksiyonu döndükten sonra eşzamansız gerçekleşebileceğini söyler. Son satır da Background'un Done kanalının nil olduğunu gösteriyor; hiç iptal edilemeyen bir context için bu beklenen davranıştır ve nil kanaldan alma sonsuza kadar beklediği için select içinde sorun çıkarmaz.
Paket belgesi cancel çağırmanın üç etkisini sayar: çocuğu ve onun çocuklarını iptal eder, ebeveynin çocuğa olan referansını kaldırır ve ilişkili zamanlayıcıları durdurur. cancel'ı çağırmamak çocuğu ve altındakileri, ebeveyn iptal edilene kadar sızdırır. Uzun yaşayan bir ebeveynden (örneğin Background) türetilmiş context'lerde bu, program boyunca biriken bir sızıntı demektir.
WithTimeout ve WithDeadline

WithDeadline(ebeveyn, t), son tarihi en geç t olacak şekilde ayarlanmış bir context döndürür. Ebeveynin son tarihi zaten daha erkense sonuç anlamsal olarak ebeveynle aynıdır; çocuk, ebeveyninden daha uzun süre tanıyamaz. WithTimeout(ebeveyn, d) ise WithDeadline(ebeveyn, time.Now().Add(d)) ile aynıdır. Süre dolduğunda Err, context.DeadlineExceeded döndürür; cancel ile iptal edilen bir context'te ise context.Canceled döner. Hata karşılaştırmasında errors.Is kullanın:
package main
import (
"context"
"errors"
"fmt"
"time"
)
func rapor(ctx context.Context) (string, error) {
select {
case <-time.After(200 * time.Millisecond): // yavaş iş
return "rapor hazır", nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
defer cancel()
_, ok := ctx.Deadline()
fmt.Println("son tarih var:", ok)
_, err := rapor(ctx)
fmt.Println(err)
fmt.Println(errors.Is(err, context.DeadlineExceeded))
}
// Çıktı:
// son tarih var: true
// context deadline exceeded
// trueDeadline metodu, işe başlamadan önce ne kadar süre kaldığını görmek için kullanışlıdır. Blog yazısı, kalan süre çok azsa işe hiç başlamamanın daha mantıklı olabileceğini ve son tarihin G/Ç işlemlerinde zaman aşımı ayarlamak için de kullanılabileceğini belirtir. Hataların sarmalanması ve errors.Is için Go hata yönetimi yazısına bakabilirsiniz.
cancel'ı çağırmamak ve go vet
WithCancel, WithTimeout ve WithDeadline'ın döndürdüğü cancel fonksiyonu kaynakları serbest bırakır; belge, işlemler biter bitmez çağrılmasını ister. WithTimeout kullanan kodda bile süre dolmasını beklemek yerine iş erken biterse cancel zamanlayıcıyı hemen durdurur. Paket belgesine göre go vet, CancelFunc'ların bütün kontrol akışı yollarında kullanıldığını denetler. Fonksiyonu alt çizgiyle atarsanız vet şu uyarıyı verir:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, _ := context.WithTimeout(context.Background(), time.Second)
fmt.Println(ctx.Err())
}./main.go:10:7: the cancel function returned by context.WithTimeout should be called, not discarded, to avoid a context leakİptal nedeni: WithCancelCause ve Cause
Err yalnızca iki değerden birini döndürür ve iptalin neden yapıldığını söylemez. Go 1.20 ile gelen WithCancelCause, error alan bir CancelCauseFunc döndürür; bu fonksiyona verdiğiniz hata, context'e iptal nedeni olarak kaydedilir ve context.Cause(ctx) ile okunur. Go 1.21 ile gelen WithDeadlineCause ve WithTimeoutCause ise süre dolduğunda kullanılacak nedeni baştan alır:
package main
import (
"context"
"errors"
"fmt"
"time"
)
var errKota = errors.New("kota aşıldı")
func main() {
ctx, cancel := context.WithCancelCause(context.Background())
cancel(errKota)
<-ctx.Done()
fmt.Println(ctx.Err()) // Err yine Canceled döner
fmt.Println(context.Cause(ctx)) // asıl neden
cancel(errors.New("ikinci neden")) // zaten iptal: neden değişmez
fmt.Println(context.Cause(ctx))
ctx2, cancel2 := context.WithTimeoutCause(context.Background(),
10*time.Millisecond, errors.New("ödeme servisi yavaş"))
defer cancel2()
<-ctx2.Done()
fmt.Println(ctx2.Err())
fmt.Println(context.Cause(ctx2))
ctx3, cancel3 := context.WithCancel(context.Background())
fmt.Println(context.Cause(ctx3)) // henüz iptal yok: nil
cancel3()
<-ctx3.Done()
fmt.Println(context.Cause(ctx3)) // neden verilmedi: Err ile aynı
}
// Çıktı:
// context canceled
// kota aşıldı
// kota aşıldı
// context deadline exceeded
// ödeme servisi yavaş
// <nil>
// context canceledÇıktı, belgedeki kuralları doğrular. Err nedenden bağımsız olarak Canceled ya da DeadlineExceeded döndürmeye devam eder; neden yalnızca Cause ile görülür. İlk iptal nedeni belirler: context zaten iptal edilmişse CancelCauseFunc nedeni değiştirmez. Neden verilmemişse Cause, Err ile aynı değeri, henüz iptal yoksa nil döndürür. WithTimeoutCause ve WithDeadlineCause'un döndürdüğü cancel fonksiyonu ise neden ayarlamaz; neden yalnızca süre dolduğunda kullanılır. Nedenin iptal edilen context'in çocuklarından da okunabildiğini unutmayın.
WithValue: isteğe özgü değerler
WithValue(ebeveyn, anahtar, değer), ebeveyni gösteren ve anahtara bağlı bir değer taşıyan türetilmiş bir context döndürür. Belgenin kuralları nettir: context değerlerini yalnızca süreçler ve API'ler arasında taşınan isteğe özgü veriler için kullanın, fonksiyonlara isteğe bağlı parametre geçirmek için kullanmayın. Anahtar karşılaştırılabilir olmalıdır ve paketler arası çakışmayı önlemek için string ya da başka bir yerleşik tip olmamalıdır. Önerilen yöntem, dışa aktarılmayan bir anahtar tipi tanımlayıp değere tip güvenli erişim fonksiyonları sağlamaktır:
package main
import (
"context"
"fmt"
)
// Dışa aktarılmayan anahtar tipi: başka paketlerin anahtarlarıyla çakışmaz.
type anahtar int
const istekIDAnahtari anahtar = 0
func IstekIDEkle(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, istekIDAnahtari, id)
}
func IstekID(ctx context.Context) (string, bool) {
id, ok := ctx.Value(istekIDAnahtari).(string)
return id, ok
}
func isle(ctx context.Context) {
if id, ok := IstekID(ctx); ok {
fmt.Println("istek:", id)
return
}
fmt.Println("istek kimliği yok")
}
func main() {
ctx := IstekIDEkle(context.Background(), "a1b2")
isle(ctx)
isle(context.Background())
// Değer ebeveynden çocuğa akar, iptal edilebilir çocuk da görür.
cocuk, cancel := context.WithCancel(ctx)
defer cancel()
isle(cocuk)
}
// Çıktı:
// istek: a1b2
// istek kimliği yok
// istek: a1b2Değerler ebeveynden çocuğa doğru görünür; çocuk context'e eklenen bir değer ebeveynden okunamaz. Belge, interface değerine atarken bellek ayırmayı önlemek için anahtarların sıklıkla somut tipi struct{} olan değerler olduğunu da not eder. Context'te taşınan verinin birden çok goroutine tarafından eşzamanlı kullanıma uygun olması gerektiğini blog yazısı ayrıca vurgular.
AfterFunc: iptalden sonra çalışacak kod
Go 1.21 ile gelen context.AfterFunc(ctx, f), context iptal edildikten sonra f'yi kendi goroutine'inde çağırır; context zaten iptal edilmişse f hemen çağrılır. Aynı context üzerinde birden çok AfterFunc kaydı birbirinden bağımsız çalışır. Döndürülen stop fonksiyonu bağı koparır ve f'nin çalışmasını engellediyse true döndürür:
package main
import (
"context"
"fmt"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
bitti := make(chan struct{})
context.AfterFunc(ctx, func() {
fmt.Println("iptal sonrası temizlik çalıştı")
close(bitti)
})
stop := context.AfterFunc(ctx, func() {
fmt.Println("bu hiç çalışmaz")
})
fmt.Println("ikinci kayıt durduruldu:", stop())
cancel()
<-bitti // AfterFunc kendi goroutine'inde çalışır, bitmesini bekle
}
// Çıktı:
// ikinci kayıt durduruldu: true
// iptal sonrası temizlik çalıştıstop false döndürürse ya context iptal edilmiş ve f kendi goroutine'inde başlamıştır ya da f zaten durdurulmuştur. stop, f'nin bitmesini beklemez; bitişi bilmeniz gerekiyorsa örnekteki gibi bir kanalla açıkça koordine etmeniz gerekir. Paket belgesindeki örnekler AfterFunc'ı, iptal geldiğinde bir ağ bağlantısının okuma son tarihini öne çekmek ve iki context'in iptalini birleştirmek için kullanır.
WithoutCancel: iptali miras almayan context
Go 1.21 ile gelen WithoutCancel(ebeveyn), ebeveyni gösteren ama ebeveyn iptal edildiğinde iptal edilmeyen bir context döndürür. Dönen context'in son tarihi ve hatası yoktur, Done kanalı nil'dir ve Cause nil döndürür. Ebeveynin değerleri ise korunur:
package main
import (
"context"
"fmt"
)
type anahtar struct{}
func main() {
ebeveyn, cancel := context.WithCancel(
context.WithValue(context.Background(), anahtar{}, "kullanıcı-7"))
bagimsiz := context.WithoutCancel(ebeveyn)
cancel()
<-ebeveyn.Done()
fmt.Println("ebeveyn:", ebeveyn.Err())
fmt.Println("bagimsiz:", bagimsiz.Err())
fmt.Println("Done nil:", bagimsiz.Done() == nil)
fmt.Println("değer:", bagimsiz.Value(anahtar{})) // değerler korunur
}
// Çıktı:
// ebeveyn: context canceled
// bagimsiz: <nil>
// Done nil: true
// değer: kullanıcı-7Bu fonksiyon, isteğin değerlerini (izleme kimliği gibi) kullanmaya devam etmesi gereken ama istek bitince yarıda kesilmemesi gereken işler için düşünülebilir. Böyle bir işe genellikle kendi süre sınırını WithTimeout ile yeniden vermeniz gerekir; aksi halde iptal edilemeyen bir iş başlatmış olursunuz.
Kullanım kuralları

Paket belgesi, paketler arasında tutarlı arayüzler ve statik analiz araçlarının context akışını denetleyebilmesi için şu kuralları koyar:
- Context'i bir yapı tipinin içinde saklamayın; ihtiyaç duyan her fonksiyona açıkça geçirin.
- Context ilk parametre olsun ve genellikle
ctxadını taşısın:func Yap(ctx context.Context, arg Arg) error. - Fonksiyon izin verse bile nil context geçirmeyin; emin değilseniz
context.TODO()kullanın. - Context değerlerini yalnızca isteğe özgü veriler için kullanın, isteğe bağlı parametreler için kullanmayın.
- Aynı context farklı goroutine'lerde çalışan fonksiyonlara verilebilir; context'ler eşzamanlı kullanıma güvenlidir.
Go Blog'daki "Contexts and structs" yazısı ilk kuralın gerekçesini açar: context'i yapıda saklamak, çağıranların her çağrı için ayrı son tarih, iptal ve üst veri belirlemesini engeller ve hangi context'in hangi metoda uygulandığını belirsizleştirir. Yazı tek istisna olarak geriye uyumluluğu gösterir: net/http paketi mevcut API'yi bozmamak için context'i Request yapısında taşımayı seçmiştir.
Standart kütüphanede context
Context ile çalışan birçok standart API vardır. Gelen bir HTTP isteğinde r.Context(), istemcinin bağlantısı kapandığında, istek iptal edildiğinde (HTTP/2 ile) ya da ServeHTTP döndüğünde iptal edilir; giden isteklerde context iptali kontrol eder. Ayrıntılar için net/http web sunucusu ve HTTP istemcisi yazılarına bakın. exec.CommandContext, context komut bitmeden sonlanırsa süreci durdurur (os/exec yazısı). Go 1.24 ile gelen T.Context ve B.Context, test bittiğinde ve temizlik fonksiyonları çalışmadan önce iptal edilen bir context döndürür (test yazma). Go 1.26 sürüm notlarına göre signal.NotifyContext artık döndürdüğü context'i hangi sinyalin alındığını belirten bir hatayla, CancelCauseFunc üzerinden iptal ediyor; bu nedeni context.Cause ile okuyabilirsiniz.
Fonksiyonlar tek tabloda
| Fonksiyon | Ne yapar | Sürüm |
|---|---|---|
| Background, TODO | Boş kök context | Go 1.7 |
| WithCancel | cancel çağrılınca iptal | Go 1.7 |
| WithDeadline, WithTimeout | Son tarih ya da süre dolunca iptal | Go 1.7 |
| WithValue | Anahtar ve değer ekler | Go 1.7 |
| WithCancelCause, Cause | İptal nedenini kaydeder ve okur | Go 1.20 |
| WithDeadlineCause, WithTimeoutCause | Süre dolma nedeni verir | Go 1.21 |
| AfterFunc | İptalden sonra f'yi çalıştırır | Go 1.21 |
| WithoutCancel | Değerleri korur, iptali miras almaz | Go 1.21 |
Sık yapılan hatalar
- cancel'ı çağırmamak: Türetilen context ve zamanlayıcısı ebeveyn iptal edilene kadar yaşar.
defer cancel()yazmayı alışkanlık haline getirin; vet bu hatayı yakalar. - Done'ı dinlemeyen kod yazmak: Context'i parametre olarak almak tek başına iptal sağlamaz; uzun süren döngü ve beklemeler
ctx.Done()'ı kontrol etmelidir. - Err'i doğrudan == ile karşılaştırmak: Hatalar sarmalanmış olabilir;
errors.Is(err, context.Canceled)kullanın. - Context'i yapı alanında saklamak: Her çağrı için ayrı son tarih ve iptal verilemez.
- string anahtar kullanmak: Başka paketlerle çakışabilir; dışa aktarılmayan özel bir tip kullanın.
- Context'i isteğe bağlı parametre torbası yapmak: Fonksiyon imzasında açıkça görünmesi gereken bağımlılıkları gizler.
- nil context geçirmek: Kullanan kod
ctx.Done()çağırdığında panic eder;context.TODO()kullanın.
Sık Sorulan Sorular
context.Background ile context.TODO arasında fark var mı?
Davranış olarak ikisi de boş, iptal edilmeyen bir context döndürür. Fark niyettedir: Background bilinçli olarak seçilmiş en üst context'tir, TODO ise doğru context henüz belli olmadığında bırakılan bir işarettir.
cancel fonksiyonunu iki kez çağırmak sorun olur mu?
Hayır. Belgeye göre ilk çağrıdan sonraki çağrılar hiçbir şey yapmaz ve CancelFunc birden çok goroutine tarafından aynı anda çağrılabilir. Bu yüzden hem defer cancel() yazmak hem de iş bittiğinde erken çağırmak güvenlidir.
Çocuk context, ebeveynden daha uzun bir süre alabilir mi?
Hayır. WithDeadline son tarihi "en geç" verilen zamana ayarlar; ebeveynin son tarihi daha erkense ebeveynin son tarihi geçerlidir. Ebeveynin iptalinden kurtulmanın tek yolu WithoutCancel'dır.
ctx.Err() ne zaman nil olmaz?
Done kanalı kapanana kadar Err nil döndürür. Kanal kapandıktan sonra Canceled ya da DeadlineExceeded döner ve sonraki çağrılar aynı hatayı döndürmeye devam eder.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Go öğrenme rehberi · select ve zaman aşımı · Go goroutine · Go hata yönetimi · net/http web sunucusu · HTTP istemcisi · os/exec komut çalıştırma · sync paketi