context
context bağlam olarak adlandırılır ve orijinal tasarım amacı, özellikle ebeveyn ve çocuk goroutine'ler arasında, çoklu goroutine'ler arasında sinyal ve bazı basit verileri iletmektir. HTTP isteklerini işleme, görev zamanlaması, veritabanı sorguları vb. senaryolarda yaygın olarak kullanılır. Özellikle mikroservis mimarilerinde, gRPC işlem süreci ve ağ metadata iletimi, bağlantı kontrol işlemleri vb. için context kullanır.
package main
import (
"context"
"fmt"
"os/signal"
"syscall"
"time"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM)
defer stop()
for {
select {
case <-ctx.Done():
fmt.Println("terminate")
return
default:
}
fmt.Println("running")
time.Sleep(100 * time.Millisecond)
}
}Yukarıdaki örnekte, işlem sinyalleri context aracılığıyla iletilir. Bir sinyal alındığında, program kendini sonlandırır. Bu da context'in bir uygulama senaryosudur.
Yapı
context.Context somut bir uygulama değil, bir dizi yöntem tanımlayan bir arayüzdür:
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}Deadline(), bir son tarih ve bitip bitmediğini belirten bir boolean döndürürDone(), sonlandırma mesajlarını bildirmek için kullanılan bir kanal döndürürErr(), context kapanma nedenini döndürürValue(), anahtara göre belirtilen değeri alır
Standart kütüphanede aşağıdaki kullanılabilir context'ler vardır:
Background, boş context, genellikle context'lerin kök düğümü olarak kullanılırWithCancel,WithCancelCause, iptal edilebilir contextWithDeadline,WithDeadlineCause, son tarihli contextWithTimeout,WithTimeoutCause, zaman aşımı süreli contextWithValue, değer taşıyabilen context
Özellikle, ana uygulamalar şunlardır:
timerCtxcancelCtxemptyCtx
Yani çekirdek işlevselliği dört noktadır:
- İptal
- Son tarih
- Değer geçirme
- Yayılma
Bu noktaları anlamak temelde context'in nasıl çalıştığını açıklığa kavuşturur.
İptal
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
cause error // set to non-nil by the first cancel call
}
func withCancel(parent Context) *cancelCtx {
if parent == nil {
panic("cannot create context from nil parent")
}
c := &cancelCtx{}
c.propagateCancel(parent, c)
return c
}cancelCtx'in çekirdeği propagateCancel metodudur, bu iptal edilebilir davranışı ebeveyn ve çocuk context'lere yaymaktan sorumludur.
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
...
}İlk olarak, ebeveyn context'in iptal edilip edilemeyeceğini kontrol eder. Edilemiyorsa, doğrudan döner.
godone := parent.Done() if done == nil { return // parent is never canceled }Döner ve ebeveyn context'in zaten iptal edilip edilmediğini kontrol eder. Evet ise, tüm çocuk context'leri iptal eder.
goselect { case <-done: // parent is already canceled child.cancel(false, parent.Err(), Cause(parent)) return default: }Ebeveyn context'i
cancelCtxtipine dönüştürmeye çalışır. Başarılı olursa, mevcut context'i ebeveyn context'inchildren'ına ekler.goif p, ok := parentCancelCtx(parent); ok { // parent is a *cancelCtx, or derives from one. p.mu.Lock() if p.err != nil { // parent has already been canceled child.cancel(false, p.err, p.cause) } else { if p.children == nil { p.children = make(map[canceler]struct{}) } p.children[child] = struct{}{} } p.mu.Unlock() return }afterFuncertipine dönüştürmeye çalışır. Başarılı olursa, mevcut context'i iptal etme yöntemini ebeveyn context'inAfterFunc'una kaydeder.goif a, ok := parent.(afterFuncer); ok { // parent implements an AfterFunc method. c.mu.Lock() stop := a.AfterFunc(func() { child.cancel(false, parent.Err(), Cause(parent)) }) c.Context = stopCtx{ Context: parent, stop: stop, } c.mu.Unlock() return }Eğer hala çalışmıyorsa,
Donekanalını dinlemek için ayrı bir goroutine başlatır. Bir sinyal alındığında, çocuk context'i iptal eder.gogo func() { select { case <-parent.Done(): child.cancel(false, parent.Err(), Cause(parent)) case <-child.Done(): } }()
Sonra cancelCtx.cancel metodu nihayetinde çocuk context'leri iptal etmekten sorumludur:
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
...
}Akışı şöyledir:
Mevcut context'in zaten iptal edilip edilmediğini kontrol eder.
goif err == nil { panic("context: internal error: missing cancel error") } if cause == nil { cause = err } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // already canceled }donekanalını kapatır ve kapanma bildirimi gönderir.goc.err = err c.cause = cause d, _ := c.done.Load().(chan struct{}) if d == nil { c.done.Store(closedchan) } else { close(d) }Dolaşır ve çocuk context'leri bildirir.
gofor child := range c.children { child.cancel(false, err, cause) } c.children = nil c.mu.Unlock()Son olarak, geçirilen parametreye göre ebeveyn context'ten kaldırılıp kaldırılmayacağını belirler.
goif removeFromParent { removeChild(c.Context, c) }
Son Tarih
WithTimeout ve WithDeadline ikisi de son tarihli context'lerdir. Aynı tiptedirler, sadece anlamları farklıdır ve ikisi de cancelCtx'e dayanır.
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}WithDeadlineCause son tarihli context'ler oluşturmaktan sorumludur:
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
...
}Akışı şöyledir:
Son tarihi kontrol eder. Eğer ebeveyn son tarihi mevcut son tarihten önceyse, o zaman ebeveyn context kesinlikle mevcut context'ten önce iptal edilecektir, bu yüzden doğrudan bir
cancelCtxtipi context oluşturur.goif parent == nil { panic("cannot create context from nil parent") } if cur, ok := parent.Deadline(); ok && cur.Before(d) { // The current deadline is already sooner than the new one. return WithCancel(parent) }timerCtxoluşturur ve çocuk context'lere yayar.goc := &timerCtx{ deadline: d, } c.cancelCtx.propagateCancel(parent, c)Mevcut son tarihi hesaplar. Eğer zaten süresi dolmuşsa, doğrudan iptal eder.
godur := time.Until(d) if dur <= 0 { c.cancel(true, DeadlineExceeded, cause) // deadline has already passed return c, func() { c.cancel(false, Canceled, nil) } }Eğer süresi dolmamışsa, son tarihte mevcut context'i iptal etmek için
time.AfterFunckullanır.goc.mu.Lock() defer c.mu.Unlock() if c.err == nil { c.timer = time.AfterFunc(dur, func() { c.cancel(true, DeadlineExceeded, cause) }) } return c, func() { c.cancel(true, Canceled, nil) }
timerCtx için, context'i iptal etme yöntemi sadece timer'ı durdurur ve ayrıca çocuk context'leri de durdurur.
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
c.cancelCtx.cancel(false, err, cause)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}Değer Geçirme
valueCtx context'lerde değerleri geçirebilir ve alabilir:
type valueCtx struct {
Context
key, val any
}
func WithValue(parent Context, key, val any) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}Çekirdeği value fonksiyonunda yatar. Diğer context somut uygulamaları da değer almak için bu fonksiyona girer, örneğin:
func (c *valueCtx) Value(key any) any {
if c.key == key {
return c.val
}
return value(c.Context, key)
}
func (c *cancelCtx) Value(key any) any {
if key == &cancelCtxKey {
return c
}
return value(c.Context, key)
}value fonksiyonu belirtilen anahtarın değerini bulmak için sürekli yukarı doğru yineleyen büyük bir for döngüsüdür:
func value(c Context, key any) any {
for {
switch ctx := c.(type) {
case *valueCtx:
if key == ctx.key {
return ctx.val
}
c = ctx.Context
case *cancelCtx:
if key == &cancelCtxKey {
return c
}
c = ctx.Context
case withoutCancelCtx:
if key == &cancelCtxKey {
// This implements Cause(ctx) == nil
// when ctx is created using WithoutCancel.
return nil
}
c = ctx.c
case *timerCtx:
if key == &cancelCtxKey {
return &ctx.cancelCtx
}
c = ctx.Context
case backgroundCtx, todoCtx:
return nil
default:
return c.Value(key)
}
}
}Farklı Context tiplerinin işlenmesi:
*valueCtx: Eğerkeymevcut Context'inkey'i ile eşleşirse,key'e karşılık gelenval'i döndürür.*cancelCtx: EğerkeycancelCtxKeyile eşleşirse, mevcutcancelCtx'i döndürür, yani kendini döndürür.withoutCancelCtx: İptal işlevi olmayan bir Context'i temsil eder. EğerkeycancelCtxKeyile eşleşirse,nildöndürür.*timerCtx: EğerkeycancelCtxKeyile eşleşirse, ilişkilicancelCtx'i döndürür.backgroundCtxvetodoCtx: Genellikle ekstra değer taşımayan özel Context tipleridir. Bu iki tip ile karşılaşıldığında, doğrudannildöndürür.- Bilinmeyen tip ise, aramaya devam etmek için
Valuemetodunu çağırmaya devam eder.
Özet
Bu context'lerin çekirdeği cancelCtx'tir, bu iptal sinyallerini yayar. Diğer context tipleri ve hatta üçüncü taraf context tipleri bunun üzerine katmanlanır, bu temel üzerine çeşitli işlevsellikler uygular.
