Context-Based Programming în Go

Du-te programe care rulează mai multe computări concurente în gorutines nevoie pentru a gestiona durata lor de viață. Gurutinele runaway pot intra în bucle infinite, blochează alte gorutine de așteptare sau pot lua prea mult timp. În mod ideal, ar trebui să puteți anula gorutinele sau să le eliminați după un mod. 

Introduceți programarea bazată pe conținut. Go 1.7 a introdus pachetul de context care oferă exact acele capabilități, precum și capacitatea de a asocia valori arbitrare cu un context care călătorește cu executarea cererilor și permite comunicarea în afara benzii și transmiterea informațiilor. 

În acest tutorial, veți afla instrucțiunile de intrare și dezactivare a contextelor din Go, când și cum să le utilizați și cum să le evitați să le abuzați. 

Cine are nevoie de un context?

Contextul este o abstracție foarte utilă. Vă permite să încapsulați informații care nu sunt relevante pentru calculul de bază, cum ar fi id-ul de solicitare, jetonul de autorizare și intervalul de timp. Există mai multe avantaje ale acestei încapsulare:

  • Se separă parametrii de bază de calcul de parametrii operaționali.
  • Acesta codifică aspectele operaționale comune și cum le comunică peste granițe.
  • Acesta oferă un mecanism standard pentru a adăuga informații în afara benzii fără a schimba semnăturile.

Interfața de context

Aici este întreaga interfață de context:

tip Interfața de context Termen () (termenul limită time.Time, ok bool) Efectuat () <-chan struct Err() error Value(key interface) interface

Următoarele secțiuni explică scopul fiecărei metode.

Metoda Termen limită ()

Termenul limită returnează momentul în care munca efectuată în numele acestui context trebuie anulată. Termenul se întoarce OK == fals când nu este stabilit niciun termen. Apelurile succesive la Termenul limită returnează aceleași rezultate.

Metoda Done ()

Efectuat () returnează un canal închis când munca efectuată în numele acestui context trebuie anulată. Efectuat poate reveni la zero dacă acest context nu poate fi anulat niciodată. Apelurile succesive la Done () returnează aceeași valoare.

  • Funcția context.WithCancel () aranjează închiderea canalului Terminat când este apelată anularea. 
  • Funcția context.WithDeadline () aranjează închiderea canalului Terminat la expirarea termenului limită.
  • Funcția context.WithTimeout () aranjează ca canalul Terminat să fie închis la expirarea timpului de expirare.

Efectuată poate fi folosită în instrucțiuni selectate:

 // Stream generează valori cu DoSomething și le trimite // până când DoSomething returnează o eroare sau ctx.Done este închis. func Stream (ctx context.Context, out chan<- Value) error  for  v, err := DoSomething(ctx) if err != nil  return err  select  case <-ctx.Done(): return ctx.Err() case out <- v:   

Consultați acest articol din blogul Go pentru mai multe exemple de utilizare a unui canal pentru anulare.

Metoda Err ()

Err () returnează valoarea zero, atâta timp cât canalul este terminat. Se întoarce Anulat dacă contextul a fost anulat sau DeadlineExceeded dacă expira termenul limită al contextului sau expiră termenul de expirare. După terminarea procesului Terminat, apelurile succesive la Err () returnează aceeași valoare. Iată definițiile:

// Anulat este eroarea returnată de Context.Err când contextul // este anulat. var Anulat = erori.Noua ("context anulat") // DeadlineExceeded este eroarea returnată de Context.Err // când trece termenul limită al contextului. var DeadlineExpeded error = deadlineExceededError  

Metoda Value ()

Valoarea returnează valoarea asociată acestui context pentru o cheie sau nulă dacă nu este asociată nici o valoare cu cheia. Apelurile succesive la valoare cu aceeași tastă returnează același rezultat.

Utilizați valori de context numai pentru datele cu cerere de referință care procesează tranzițiile și limitele API, nu pentru trecerea parametrilor opționali la funcții.

O cheie identifică o valoare specifică într-un context. Funcțiile care doresc să stocheze valori în Context alocă în mod obișnuit o cheie într-o variabilă globală și utilizează acea cheie ca argument pentru context.WithValue () și Context.Value (). O cheie poate fi orice tip care sprijină egalitatea.

Context Domeniul de aplicare

Contextele au scopuri. Puteți obține domenii din alte domenii, iar domeniul de aplicare parental nu are acces la valori în domeniile derivate, dar domeniile derivate au acces la valorile scopurilor părintelui. 

Contextele formează o ierarhie. Începeți cu context.Background () sau context.TODO (). Ori de câte ori apelați cuCancel (), WithDeadline () sau WithTimeout (), creați un context derivat și primiți o funcțiune anulare. Lucrul important este că atunci când un context părinte este anulat sau expirat, toate contextele sale derivate.

Ar trebui să utilizați context.Background () în funcția principal (), init () și teste. Trebuie să utilizați context.TODO () dacă nu sunteți sigur ce context să utilizați.

Rețineți că există Background și TODO nu anulabil.

Termenele limită, orele de expirare și anularea

După cum vă amintiți, CuDeadline () și WithTimeout () returnează contextele care se anulează automat, în timp ce WithCancel () returnează un context și trebuie anulate în mod explicit. Toți aceștia returnează o funcție de anulare, deci chiar dacă termenul de expirare / expirare nu a expirat încă, este posibil să anulați orice context derivat. 

Să examinăm un exemplu. Mai întâi, aici este funcția contextDemo () cu un nume și un context. Rulează într-o buclă infinită, tipărind consola numele său și termenul limită al contextului, dacă este cazul. Apoi doarme doar o secundă.

pachetul principal de import ("fmt" "context" "time") func contextDemo (șirul de nume, ctx context.Context) if ok fmt.Println (numele expirat la: .Print (nume, "nu are termen limită") time.Sleep (time.Second)

Funcția principală creează trei contexte: 

  • timeoutContext cu un timeout de trei secunde
  • un CancelContext care nu expiră
  • deadlineContext, care este derivat din CancelContext, cu un termen limită de patru ore de acum

Apoi, lansează funcția contextDemo ca trei gorutine. Toți difuzează simultan și imprimă mesajul în fiecare secundă. 

Funcția principală așteaptă apoi ca gorutina cu timeoutCancel să fie anulată citirea din canalul Done () (va bloca până când va fi închisă). Odată ce termenul de expirare expiră după trei secunde, principalul () cheamă cancelFunc () care anulează gorutina cu cancelContext, precum și cu ultima gorutină cu contextul de termen limită derivat de patru ore.

func () timeout: = 3 * time.Second deadline: = time.Now () .Adăugați (4 * time.Hour) timeOutContext, _: = context.WithTimeout (context.Background (), timeout) cancelContext, cancelFunc : = context.WithCancel (context.Background ()) deadlineContext, _: = context.WithDeadline (cancelContext, deadline) merge contextDemo ("[timeoutContext]", timeOutContext) go contextDemo ("cancelContext" "[deadlineContext]", deadlineContext) // Așteptați expirarea expirării <- 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… ")  

Aici este rezultatul:

[cancelContext] nu are termen limită [deadlineContext] expiră la: 2017-07-29 09: 06: 02.34260363 [timeoutContext] expiră la: 2017-07-29 05: 06: 05.342603759 [cancelContext] nu are termen limită [timeoutContext] expiră la: 2017-07-29 05: 06: 05.342603759 [deadlineContext] expiră la: 2017-07-29 09: 06: 02.34260363 [cancelContext] nu are termen limită [timeoutContext] 06: 05.342603759 [deadlineContext] expiră la: 2017-07-29 09: 06: 02.34260363 Anularea contextului de anulare ... Contextul de anulare a fost anulat ... Contextul termenului limită a fost anulat ... 

Transmiterea valorilor în context

Puteți atașa valori la un context folosind funcția WithValue (). Rețineți că contextul original este returnat, nu un context derivat. Puteți citi valorile din context folosind metoda Value (). Să ne modificăm funcția demo pentru a obține numele din context în loc să îl transmitem ca parametru:

functie contextDemo (ctx context.Context) deadline, ok: = ctx.Deadline () nume: = ctx.Value ("nume") pentru if ok fmt.Println  altceva fmt.Println (nume, "nu are termen limită") time.Sleep (time.Second) 

Și să modificăm funcția principală pentru atașarea numelui cu ajutorul funcției WithValue ():

go contextDemo (context.WithValue (timeOutContext, "nume", "[timeoutContext]")) merge contextDemo (context.WithValue (cancelContext, "nume", "[cancelContext]") go contextDemo (context.WithValue (deadlineContext, nume "," [deadlineContext] ")) 

Rezultatul rămâne același. Consultați secțiunea privind cele mai bune practici pentru câteva indicații despre folosirea adecvată a valorilor contextului.

Cele mai bune practici

Câteva bune practici au apărut în jurul valorii de context:

  • Evitați să treceți argumentele funcțiilor în valorile contextului.
  • Funcțiile care doresc să stocheze valori în Context alocă în mod obișnuit o cheie într-o variabilă globală.
  • Pachetele ar trebui să definească cheile ca un tip neexportat pentru a evita coliziunile.
  • Ambele pachete care definesc o cheie de context trebuie să furnizeze accesoriile de tip sigur pentru valorile stocate utilizând cheia respectivă.

Contextul solicitării HTTP

Unul dintre cele mai utile cazuri de utilizare pentru contexte este transmiterea informațiilor împreună cu o cerere HTTP. Aceste informații pot include un ID al cererii, acreditările de autentificare și multe altele. În pachetul Go 1.7, pachetul standard net / http a profitat de pachetul de context obținând standardizarea și a adăugat suportul contextului direct la obiectul cerere:

func (r * Cerere) Context () context.Context func (r * Request) CuContext (ctx context.Context) * Solicitare 

Acum este posibil să atașați un id de cerere de la anteturi până la ultimul handler în mod standard. Funcția WithRequestID () extrage un ID de solicitare din antetul "X-Request-ID" și generează un nou context cu id-ul de solicitare dintr-un context existent pe care îl utilizează. Apoi îl transmite conducătorului din lanț. Funcția publică GetRequestID () oferă acces la dispozitivele care pot fi definite în alte pachete.

const requestIDKey int = 0 func WithRequestID (următorul http.Handler) http.Handler retur http.HandlerFunc (func (rw http.ResponseWriter, req * http.Request) // Extrageți ID-ul cererii din antetul cererii reqID: = req.Header .Get ("X-Request-ID") // Creați un context nou din contextul solicitării cu // ID-ul solicitării ctx: = context.WithValue (req.Context (), requestIDKey, reqID) context req = req.WithContext (ctx) // Să preluăm următorul handler din lanț următor.ServeHTTP (rw, req)) func GetRequestID (ctx context.Context) șir ctx.Value (requestIDKey). string) func Handle (rw http.ResponseWriter, req * http.Request) reqID: = GetRequestID (req.Context ()) ... func () handler: = WithRequestID (http.HandlerFunc) http. ListenAndServe ("/", handler) 

Concluzie

Programarea bazată pe context oferă o modalitate standard și bine susținută de a aborda două probleme comune: gestionarea duratei de viață a gorutinelor și trecerea informațiilor în afara benzii de-a lungul unui lanț de funcții. 

Urmați cele mai bune practici și utilizați contextele în contextul potrivit (vedeți ce am făcut acolo) și codul dvs. se va îmbunătăți considerabil.

Cod