Go programmer som kjører flere samtidige beregninger i goroutines trenger å styre livet deres. Runaway goroutines kan komme inn i uendelige løkker, hindre andre ventende goroutiner, eller bare ta for lang tid. Ideelt sett bør du kunne avbryte goroutiner eller få dem til å gå ut etter en mote.
Skriv inn innholdsbasert programmering. Go 1.7 introduserte kontekstpakken, som gir nøyaktig disse evnene, samt evnen til å knytte vilkårlig verdier med en kontekst som beveger seg med utførelsen av forespørsler og tillater utgående kommunikasjon og informasjon som passerer.
I denne opplæringen lærer du innspillene i sammenhenger i Go, når og hvordan du bruker dem, og hvordan du unngår å misbruke dem.
Konteksten er en veldig nyttig abstraksjon. Den lar deg inkapslere informasjon som ikke er relevant for kjerneberegningen som forespørsels-ID, autorisasjons token og timeout. Det er flere fordeler med denne innkapslingen:
Her er hele kontekstgrensesnittet:
skriv inn kontekstgrensesnitt Deadline () (deadline time.Time, ok bool) Done () <-chan struct Err() error Value(key interface) interface
Følgende avsnitt forklarer formålet med hver metode.
Fristen returnerer tiden når arbeid utført på vegne av denne sammenhengen skal avlyses. Frist for retur ok == false
når ingen tidsfrist er angitt. Etterfølgende samtaler til tidsfrist gir de samme resultatene.
Ferdig () returnerer en kanal som er stengt når arbeid utført på vegne av denne konteksten skal avbrytes. Ferdig kan returnere null hvis denne konteksten aldri kan kanselleres. Etterfølgende samtaler til Ferdig () returnerer samme verdi.
Ferdig kan brukes i utvalgte setninger:
// Stream genererer verdier med DoSomething og sender dem // til ut til DoSomething returnerer en feil eller ctx.Done er // lukket. func Stream (ctx context.Context, ut chan<- Value) error for v, err := DoSomething(ctx) if err != nil return err select case <-ctx.Done(): return ctx.Err() case out <- v:
Se denne artikkelen fra Go-bloggen for flere eksempler på hvordan du bruker en ferdig kanal for avbestilling.
Err () returnerer null så lenge ferdig kanal er åpen. Det kommer tilbake avbrutt
hvis konteksten ble kansellert eller DeadlineExceeded
hvis kontekstens frist gikk eller tidsavbrudd utløpt. Etter at Klar er lukket, returnerer påfølgende samtaler til Err () samme verdi. Her er definisjonene:
// Avbryt er feilen returnert av Context.Err når // konteksten er avbrutt. var Canceled = errors.New ("kontekst avbrutt") // DeadlineExceeded er feilen returnert av Context.Err // når kontekstens frist går. var DeadlineExceeded error = deadlineExceededError
Verdi returnerer verdien som er knyttet til denne konteksten for en nøkkel, eller null hvis ingen verdi er knyttet til nøkkelen. Etterfølgende samtaler til Verdi med samme nøkkel returnerer det samme resultatet.
Bruk kun kontekstverdier for forespørselsskannede data som overfører prosesser og API-grenser, ikke for å overføre valgfrie parametere til funksjoner.
En nøkkel identifiserer en bestemt verdi i en kontekst. Funksjoner som ønsker å lagre verdier i Kontekst, tilordner vanligvis en nøkkel i en global variabel og bruker den nøkkelen som argumentet til context.WithValue () og Context.Value (). En nøkkel kan være hvilken som helst type som støtter likestilling.
Kontekster har rekkevidder. Du kan oppnå omfang fra andre områder, og foreldreområdet har ingen tilgang til verdier i avledede omfang, men avledede omfang har tilgang til foreldrenes omfangsverdier.
Konteksten danner et hierarki. Du starter med context.Background () eller context.TODO (). Når du ringer WithCancel (), WithDeadline () eller WithTimeout (), oppretter du en avledet kontekst og mottar en kanselleringsfunksjon. Det viktigste er at når en overordnet kontekst er kansellert eller utløpt, er alle dens avledede sammenhenger.
Du bør bruke context.Background () i funksjonene main (), init () og tester. Du bør bruke context.TODO () hvis du ikke er sikker på hvilken kontekst du vil bruke.
Legg merke til at Bakgrunn og TODO er ikke avbrytes.
Som du husker, WithDeadline () og WithTimeout () returkontekster som blir kansellert automatisk, mens WithCancel () returnerer en kontekst og må avbrytes eksplisitt. Alle av dem returnerer en kanselleringsfunksjon, så selv om timeout / deadline ikke utløper ennå, kan du likevel avbryte en avledet kontekst.
La oss undersøke et eksempel. Først, her er contextDemo () -funksjonen med et navn og en kontekst. Den går i en uendelig sløyfe, skriver ut til konsollen sitt navn og dens konteksts frist hvis det er noe. Så sover det bare et sekund.
pakke main import ("fmt" "kontekst" "tid") func contextDemo (navnestreng, ctx context.Context) for hvis ok fmt.Println (navn, "utløper på:", frist) else fmt .Println (navn, "har ingen tidsfrist") time.Sleep (time.Second)
Hovedfunksjonen skaper tre sammenhenger:
Deretter lanserer den contextDemo funksjonen som tre goroutiner. Alle løper samtidig og skriver ut meldingen hvert sekund.
Hovedfunksjonen venter deretter på goroutinen med timeoutCancel å bli kansellert ved å lese fra sin ferdige () kanal (vil blokkere til den er stengt). Når tidsavbrudd utløper etter tre sekunder, kalles main () (cancel) Func () som kansellerer goroutinen med cancelContext samt den siste goroutin med den avledede fire timers tidsrammekontekst.
func main () timeout: = 3 * time.Second deadline: = time.Now (). Legg til (4 * time.Hour) timeOutContext, _: = context.WithTimeout (context.Background (), timeout) cancelContext, cancelFunc : = context.WithCancel (context.Background ()) deadlineContext, _: = context.WithDeadline (cancelContext, deadline) gå contextDemo ("[timeoutContext]", timeOutContext) gå contextDemo ("[cancelContext]", cancelContext) go contextDemo "[deadlineContext]", deadlineContext) // Vent på timeout å utløpe <- timeOutContext.Done() // This will cancel the deadline context as well as its // child - the cancelContext fmt.Println("Cancelling the cancel context… ") cancelFunc() <- cancelContext.Done() fmt.Println("The cancel context has been cancelled… ") // Wait for both contexts to be cancelled <- deadlineContext.Done() fmt.Println("The deadline context has been cancelled… ")
Her er utgangen:
[CancelContext] har ingen tidsfrist [deadlineContext] utløper ved: 2017-07-29 09: 06: 02.34260363 [timeoutContext] utløper ved: 2017-07-29 05: 06: 05.342603759 [cancelContext] har ingen deadline [timeoutContext] vil utløper ved: 2017-07-29 05: 06: 05.342603759 [deadlineContext] utløper ved: 2017-07-29 09: 06: 02.34260363 [cancelContext] har ingen deadline [timeoutContext] utløper ved: 2017-07-29 05: 06: 05.342603759 [deadlineContext] utløper ved: 2017-07-29 09: 06: 02.34260363 Avbryter avbryt konteksten ... Avbryt konteksten er blitt kansellert ... Konfristen for tidsfrist har blitt kansellert ...
Du kan legge til verdier i en kontekst ved hjelp av funksjonen WithValue (). Legg merke til at den opprinnelige konteksten returneres, ikke en avledet kontekst. Du kan lese verdiene fra konteksten ved hjelp av metoden Value () (). La oss endre vår demo-funksjon for å få navnet sitt fra konteksten i stedet for å sende det som en parameter:
func contextDemo (ctx context.Context) deadline, ok: = ctx.Deadline () navn: = ctx.Value ("navn") for hvis ok fmt.Println (navn, "utløper på:", deadline) ellers fmt.Println (navn, "har ingen tidsfrist") time.Sleep (time.Second)
Og la oss endre hovedfunksjonen for å feste navnet via WithValue ():
gå contextDemo (context.WithValue (timeOutContext, "name", "[timeoutContext]")) go contextDemo (context.WithValue (cancelContext, "name", "[cancelContext]") gå contextDemo (context.WithValue (deadlineContext, " navn "," [deadlineContext] "))
Produksjonen forblir den samme. Se avsnittet om beste praksis for noen retningslinjer om bruk av kontekstverdier på riktig måte.
Flere gode praksiser har dukket opp rundt kontekstverdier:
En av de mest nyttige bruksområdene for sammenhenger er å sende informasjon sammen med en HTTP-forespørsel. Denne informasjonen kan inneholde en forespørsel id, autentiserings legitimasjon og mer. I Go 1.7 tok standard nettverks- / http-pakken fordel av at kontekstpakken ble "standardisert" og lagt til sammenhengsstøtte direkte til forespørselsobjektet:
func (r * Request) Kontekst () context.Context func (r * Request) WithContext (ctx context.Context) * Forespørsel
Nå er det mulig å legge ved en forespørsel id fra topptekstene helt til sluttbehandleren på en standard måte. Funksjonen WithRequestID () håndterer en forespørsels ID fra "X-Request-ID" header og genererer en ny kontekst med forespørsel id fra en eksisterende kontekst som den bruker. Den overfører den til neste handler i kjeden. Den offentlige funksjonen GetRequestID () gir tilgang til håndtere som kan defineres i andre pakker.
const requestIDKey int = 0 func WithRequestID (neste http.Handler) http.Handler return http.HandlerFunc (func (rw http.ResponseWriter, req * http.Request) // Utdrag forespørsels ID fra forespørselsoverskrift reqID: = req.Header .Get ("X-Request-ID") // Opprett ny kontekst fra forespørsel sammenheng med // forespørselen ID ctx: = context.WithValue (req.Context (), requestIDKey, reqID) // Opprett ny forespørsel med den nye kontekst req = req.WithContext (ctx) // La neste handler i kjeden ta over. next.ServeHTTP (rw, req)) func GetRequestID (ctx context.Context) streng ctx.Value (requestIDKey). streng) func Handle (rw http.ResponseWriter, req * http.Request) reqID: = GetRequestID (req.Context ()) ... func main () handler: = WithRequestID (http.HandlerFunc (Handle)) http. ListenAndServe ("/", handler)
Kontekstbasert programmering gir en standard og godt støttet måte å løse to vanlige problemer på: håndtering av goroutinernes levetid og overføring av utgående båndinformasjon over en funksjonskjede.
Følg de beste praksis og bruk sammenhenger i riktig sammenheng (se hva jeg gjorde der?) Og koden din vil bli betydelig forbedret.