Vă mulțumim că v-ați abonat

Introducere

când am început să lucrez cu canalele Go pentru prima dată, am făcut greșeala de a gândi canalele ca o structură de date. Am văzut canalele ca o coadă care oferea acces automat sincronizat între goroutines. Această înțelegere structurală m-a determinat să scriu o mulțime de coduri concurente proaste și complicate.

am învățat de-a lungul timpului că cel mai bine este să uiți de modul în care sunt structurate canalele și să te concentrezi pe modul în care se comportă. Deci, acum când vine vorba de canale, mă gândesc la un singur lucru: semnalizarea. Un canal permite unui goroutin să semnaleze unui alt goroutin despre un anumit eveniment. Semnalizarea este în centrul a tot ceea ce ar trebui să faceți cu canalele. Gândirea canalelor ca mecanism de semnalizare vă va permite să scrieți un cod mai bun, cu un comportament bine definit și mai precis.

pentru a înțelege cum funcționează semnalizarea, trebuie să înțelegem cele trei atribute ale sale:

  • Garanția livrării
  • Stat
  • cu sau fără date

aceste trei atribute lucrează împreună pentru a crea o filozofie de proiectare în jurul semnalizării. După ce am discuta despre aceste atribute, voi oferi o serie de exemple de cod care demonstrează semnalizare cu aceste atribute aplicate.

Garanția livrării

Garanția livrării se bazează pe o singură întrebare: „am nevoie de o garanție că semnalul trimis de o anumită goroutină a fost primit?”

cu alte cuvinte, având în vedere acest exemplu în listarea 1:

listare 1

01 go func() {02 p := <-ch // Receive03 }()0405 ch <- "paper" // Send

are goroutine trimiterea nevoie de o garanție că paper fiind trimis peste canalul de pe linia 05 a fost primit de goroutine pe linia 02 înainte de a trece mai departe?

pe baza răspunsului la această întrebare, veți ști care dintre cele două tipuri de canale să utilizați: Nebuffered sau tamponat. Fiecare canal oferă un comportament diferit în jurul garanțiilor de livrare.

Figura 1 : Garanția livrării

Garanția livrării

garanțiile sunt importante și, dacă nu credeți, am o mulțime de lucruri pe care vreau să vi le vând. Desigur, încerc să fac o glumă, dar nu te enervezi când nu ai garanții în viață? A avea o înțelegere puternică dacă aveți sau nu nevoie de o garanție este crucial atunci când scrieți software concurent. Pe măsură ce continuăm, veți învăța cum să decideți.

stare

comportamentul unui canal este direct influențat de starea sa actuală. Starea unui canal poate fi nulă, deschisă sau închisă.

listarea 2 de mai jos arată cum să declarați sau să plasați un canal în fiecare dintre aceste trei stări.

Listă 2

// ** nil channel// A channel is in a nil state when it is declared to its zero valuevar ch chan string// A channel can be placed in a nil state by explicitly setting it to nil.ch = nil// ** open channel// A channel is in a open state when it's made using the built-in function make.ch := make(chan string) // ** closed channel// A channel is in a closed state when it's closed using the built-in function close.close(ch)

statul determină modul în care se comportă operațiunile de trimitere și primire.

semnalele sunt trimise și recepționate printr-un canal. Nu spuneți citire/scriere deoarece canalele nu efectuează I/O.

Figura 2 : Starea

starea

când un canal este într-o stare nulă, orice încercare de trimitere sau primire pe canal se va bloca. Când un canal este într-o stare deschisă, semnalele pot fi trimise și primite. Când un canal este plasat într-o stare închisă, semnalele nu mai pot fi trimise, dar este încă posibil să primiți semnale.

aceste stări vor oferi diferitele comportamente de care aveți nevoie pentru diferitele situații pe care le întâlniți. Atunci când combinați statul cu Garanția livrării, puteți începe să analizați costurile/beneficiile pe care le suportați ca urmare a alegerilor dvs. de proiectare. În multe cazuri, veți putea, de asemenea, să identificați rapid erorile doar citind codul, deoarece înțelegeți cum se va comporta canalul.

cu și fără date

ultimul atribut de semnalizare care trebuie luat în considerare este dacă trebuie să semnalizați cu sau fără date.

semnalizați cu date efectuând o trimitere pe un canal.

Listă 3

01 ch <- "paper"

când semnalizați cu date, este de obicei pentru că:

  • un goroutine este rugat să înceapă o nouă sarcină.
  • un goroutine raportează un rezultat.

semnalați fără date prin închiderea unui canal.

Listă 4

01 close(ch)

când semnalizați fără date, este de obicei pentru că:

  • unui goroutin i se spune să oprească ceea ce fac.
  • un goroutine raportează înapoi că sunt făcute fără rezultat.
  • un goroutine raportează că a finalizat procesarea și a închis.

există excepții de la aceste reguli, dar acestea sunt cazurile majore de utilizare și cele pe care ne vom concentra în acest post. Aș considera excepțiile de la aceste reguli ca fiind un miros de cod inițial.

un beneficiu al semnalizării fără date este că o singură goroutină poate semnala mai multe goroutine simultan. Semnalizarea cu date este întotdeauna un schimb de la 1 la 1 între goroutine.

semnalizare cu date

când veți semnala cu date, există trei opțiuni de configurare a canalelor pe care le puteți alege în funcție de tipul de garanție de care aveți nevoie.

Figura 3 : Semnalizare cu date

cele trei opțiuni de canal sunt Unbuffered, tamponat > 1 sau tamponat =1.

  • garanție

    • un canal Netamponat vă oferă o garanție că un semnal trimis a fost primit.
      • deoarece primirea semnalului are loc înainte de finalizarea trimiterii semnalului.
  • nicio garanție

    • un canal tamponat cu dimensiunea > 1 nu vă oferă nicio garanție că a fost recepționat un semnal trimis.
      • deoarece trimiterea semnalului are loc înainte ca primirea semnalului să se finalizeze.
  • garanție întârziată

    • un canal tamponat de dimensiune =1 vă oferă o garanție întârziată. Poate garanta că semnalul anterior care a fost trimis a fost primit.
      • deoarece primirea primului semnal se întâmplă înainte ca trimiterea celui de-al doilea semnal să se finalizeze.

dimensiunea tamponului nu trebuie să fie niciodată un număr aleatoriu, trebuie întotdeauna calculată pentru o anumită constrângere bine definită. Nu există infinit în calcul, totul trebuie să aibă o constrângere bine definită, indiferent dacă este timp sau spațiu.

semnalizarea fără date

semnalizarea fără date este rezervată în principal pentru anulare. Acesta permite un goroutine pentru a semnala un alt goroutine pentru a anula ceea ce fac și pentru a muta pe. Anularea poate fi implementată utilizând atât canale Nebuffered, cât și canale tamponate, dar utilizarea unui canal tamponat atunci când nu vor fi trimise date este un miros de cod.

Figura 4 : Semnalizare fără date

funcția încorporată close este utilizată pentru a semnala fără date. După cum sa explicat mai sus în secțiunea de stare, puteți primi în continuare semnale pe un canal închis. De fapt, orice primire pe un canal închis nu se va bloca și operația de primire revine întotdeauna.

în majoritatea cazurilor doriți să utilizați pachetul standard library context pentru a implementa semnalizarea fără date. Pachetul context utilizează un canal Netamponat dedesubt pentru semnalizare și funcția încorporată close pentru a semnala fără date.

dacă alegeți să utilizați propriul canal pentru anulare, mai degrabă decât pachetul de context, canalul dvs. ar trebui să fie de tipul chan struct{}. Este spațiul zero, mod idiomatic de a indica un canal utilizat numai pentru semnalizare.

scenarii

cu aceste atribute în loc, cel mai bun mod de a înțelege în continuare modul în care acestea funcționează în practică este de a rula printr-o serie de scenarii de cod. Îmi place să mă gândesc la goroutines ca oameni atunci când citesc și scriu cod bazat pe canal. Această vizualizare ajută cu adevărat și o voi folosi ca ajutor mai jos.

semnal cu garanție de date – canale Nebufferate

când trebuie să știți că a fost primit un semnal trimis, intră în joc două scenarii. Acestea sunt așteptați pentru sarcină și așteptați pentru rezultat.

Scenariul 1 – așteptați sarcina

gândiți-vă să fiți manager și să angajați un nou angajat. În acest scenariu, doriți ca noul dvs. angajat să îndeplinească o sarcină, dar trebuie să aștepte până când sunteți gata. Acest lucru se datorează faptului că trebuie să le dați o bucată de hârtie înainte de a începe.

Listă 5
https://play.golang.org/p/BnVEHRCcdh

01 func waitForTask() {02 ch := make(chan string)0304 go func() {05 p := <-ch0607 // Employee performs work here.0809 // Employee is done and free to go.10 }()1112 time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)1314 ch <- "paper"15 }

pe linia 02 din lista 5, este creat un canal Netamponat cu atributul că string datele vor fi trimise cu semnalul. Apoi, pe linia 04, un angajat este angajat și i se spune să aștepte semnalul dvs. pe linia 05 înainte de a-și face munca. Linia 05 este canalul primiți, determinând angajatul să blocheze în timp ce așteptați bucata de hârtie pe care o veți trimite. Odată ce hârtia este primită de angajat, angajatul efectuează munca și apoi este terminat și liber să plece.

dvs., ca manager, lucrați concomitent cu noul dvs. angajat. Deci, după ce angajați angajatul pe linia 04, vă aflați (pe linia 12) făcând ceea ce trebuie să faceți pentru a debloca și semnaliza angajatul. Rețineți că nu se știa cât timp va dura pentru a pregăti această bucată de hârtie pe care trebuie să o trimiteți.

în cele din urmă sunteți gata să semnalizați angajatul. Pe linia 14, efectuați un semnal cu date, datele fiind acea bucată de hârtie. Deoarece un canal Unbuffered este utilizat, veți obține o garanție că angajatul a primit hârtia odată ce operațiunea de trimitere se încheie. Primirea se întâmplă înainte de trimitere.

din punct de vedere tehnic, tot ce știți este că angajatul are hârtia până la finalizarea operațiunii de trimitere a canalului. După ambele operații de canal, Planificatorul poate alege să execute orice declarație dorește. Următoarea linie de cod care este executată fie de dvs., fie de angajat este nedeterministă. Aceasta înseamnă că utilizarea declarațiilor tipărite vă poate păcăli cu privire la ordinea lucrurilor.

scenariul 2 – așteptați rezultatul

în acest scenariu următor lucrurile sunt inversate. De data aceasta doriți ca noul dvs. angajat să îndeplinească o sarcină imediat când sunt angajați și trebuie să așteptați rezultatul muncii lor. Trebuie să așteptați pentru că aveți nevoie de hârtie de la ei înainte de a putea continua.

listă 6
https://play.golang.org/p/VFAWHxIQTP

01 func waitForResult() {02 ch := make(chan string)0304 go func() {05 time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)0607 ch <- "paper"0809 // Employee is done and free to go.10 }()1112 p := <-ch13 }

pe linia 02 din lista 6, este creat un canal Netamponat cu atributul că string datele vor fi trimise cu semnalul. Apoi, pe linia 04, un angajat este angajat și este imediat pus la muncă. După ce angajați angajatul pe linia 04, vă aflați următorul pe linia 12 așteptând raportul de hârtie.

odată ce lucrarea este finalizată de angajat pe linia 05, aceștia vă trimit rezultatul pe linia 07 prin efectuarea unui canal de trimitere cu date. Deoarece acesta este un canal Nebuffered, primirea se întâmplă înainte de trimitere și angajatul este garantat că ați primit rezultatul. Odată ce angajatul are această garanție, acestea sunt făcute și libere să plece. În acest scenariu, nu aveți idee cât timp va dura angajatul pentru a termina sarcina.

Cost/beneficiu

un canal Unbuffered oferă o garanție că un semnal fiind trimis a fost primit. Acest lucru este minunat, dar nimic nu este gratuit. Costul acestei garanții este latență necunoscută. În scenariul de așteptare pentru sarcină, angajatul nu are nicio idee cât timp va dura pentru a trimite acea hârtie. În scenariul așteptați rezultatul, nu aveți idee cât timp va dura angajatul pentru a vă trimite acel rezultat.

în ambele scenarii, această latență necunoscută este ceva cu care trebuie să trăim, deoarece garanția este necesară. Logica nu funcționează fără acest comportament garantat.

semnal cu date – fără garanție – canale tamponate>1

când nu trebuie să știți că a fost primit un semnal trimis, aceste două scenarii intră în joc: Fan Out și Drop.

un canal tamponat are un spațiu bine definit care poate fi utilizat pentru a stoca datele trimise. Deci, cum decideți cât spațiu aveți nevoie? Răspundeți la aceste întrebări:

  • am o cantitate bine definită de muncă de finalizat?
    • cât de mult de lucru este acolo?
  • dacă angajatul meu nu poate ține pasul, pot renunța la orice lucrare nouă?
    • cât de multă muncă remarcabilă mă pune la capacitate?
  • ce nivel de risc sunt dispus să accept dacă programul meu se termină în mod neașteptat?
    • orice așteptare în tampon se va pierde.

dacă aceste întrebări nu au sens pentru comportamentul pe care îl modelați, este un miros de cod că utilizarea unui canal tamponat mai mare de 1 este probabil greșită.

Scenariul 1 – Fan Out

un model fan out vă permite să aruncați un număr bine definit de angajați la o problemă care lucrează concomitent. Deoarece aveți un angajat pentru fiecare sarcină, știți exact câte rapoarte veți primi. Vă puteți asigura că există spațiul potrivit în caseta dvs. pentru a primi toate aceste rapoarte. Acest lucru are avantajul că angajații dvs. nu trebuie să aștepte să trimiteți raportul. Cu toate acestea, ei trebuie să ia fiecare un viraj plasarea raportului în caseta În cazul în care ajung la caseta la sau aproape în același timp.

Imaginați-vă că sunteți din nou managerul, dar de data aceasta angajați o echipă de angajați. Aveți o sarcină individuală pe care doriți ca fiecare angajat să o îndeplinească. Pe măsură ce fiecare angajat își termină sarcina, trebuie să vă ofere un raport pe hârtie care trebuie plasat în cutia dvs. de pe birou.

listă 7
https://play.golang.org/p/8HIt2sabs_

01 func fanOut() {02 emps := 2003 ch := make(chan string, emps)0405 for e := 0; e < emps; e++ {06 go func() {07 time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)08 ch <- "paper"09 }()10 }1112 for emps > 0 {13 p := <-ch14 fmt.Println(p)15 emps--16 }17 }

pe linia 03 din lista 7, se creează un canal tamponat cu atributul că string datele vor fi trimise cu semnalul. De data aceasta canalul este creat cu 20 de tampoane datorită variabilei emps declarată pe linia 02.

între liniile 05 până la 10, 20 de angajați sunt angajați și ajung imediat la muncă. Nu aveți idee cât timp va dura fiecare angajat pe linia 07. Apoi, pe linia 08, angajații trimit raportul de hârtie, dar de data aceasta trimiterea nu blochează așteptarea unei recepții. Deoarece există loc în cutie pentru fiecare angajat, trimiterea pe canal concurează doar cu alți angajați care ar putea dori să-și trimită raportul în același timp sau aproape în același timp.

Codul dintre liniile 12-16 este tot ce. Aici așteptați ca toți cei 20 de angajați să-și termine munca și să-și trimită raportul. Pe linia 12, vă aflați într-o buclă și pe linia 13 sunteți blocat într-un canal primiți în așteptarea rapoartelor. Odată ce un raport este primit, raportul este tipărit pe linia 14 și variabila contor local este decrementat pentru a indica un angajat se face.

scenariul 2 – Drop

un model de cădere vă permite să aruncați munca atunci când angajatul(angajații) dvs. sunt la capacitate. Acest lucru are avantajul de a continua să accepte munca de la clienții dvs. și de a nu aplica niciodată contrapresiune sau latență în acceptarea acelei lucrări. Cheia aici este să știi când ești cu adevărat la capacitate, astfel încât să nu te angajezi sub sau peste cantitatea de muncă pe care o vei încerca să o faci. De obicei, testarea integrării sau valorile sunt ceea ce aveți nevoie pentru a vă ajuta să identificați acest număr.

Imaginați-vă că sunteți din nou managerul și angajați un singur angajat pentru a face munca. Aveți o sarcină individuală pe care doriți să o îndeplinească angajatul. Pe măsură ce angajatul își termină sarcina, nu-ți pasă să știi că au terminat. Tot ce este important este dacă puteți sau nu să plasați lucrări noi în cutie. Dacă nu puteți efectua trimiterea, atunci știți că cutia dvs. este plină și angajatul este la capacitate. În acest moment, noua lucrare trebuie aruncată, astfel încât lucrurile să poată continua să se miște.

listă 8
https://play.golang.org/p/PhFUN5itiv

01 func selectDrop() {02 const cap = 503 ch := make(chan string, cap)0405 go func() {06 for p := range ch {07 fmt.Println("employee : received :", p)08 }09 }()1011 const work = 2012 for w := 0; w < work; w++ {13 select {14 case ch <- "paper":15 fmt.Println("manager : send ack")16 default:17 fmt.Println("manager : drop")18 }19 }2021 close(ch)22 }

pe linia 03 din lista 8, se creează un canal tamponat cu atributul că string datele vor fi trimise cu semnalul. De data aceasta canalul este creat cu 5 tampoane datorită Constantei cap declarată pe linia 02.

între liniile 05-09 un singur angajat este angajat să se ocupe de muncă. Un for range este utilizat pentru primirea canalului. De fiecare dată când se primește o bucată de hârtie, aceasta este procesată pe linia 07.

între liniile 11-19 încercați să trimiteți 20 de bucăți de hârtie angajatului dvs. De data aceasta, o instrucțiune select este utilizată pentru a efectua trimiterea în interiorul primului case de pe linia 14. Deoarece clauza default este utilizată în interiorul select de pe linia 16, Dacă trimiterea va bloca deoarece nu mai există spațiu în tampon, trimiterea este abandonată prin executarea liniei 17.

în cele din urmă pe linia 21, funcția încorporată close este apelată împotriva canalului. Acest lucru va semnala fără date angajatului pe care l-au terminat și liber să plece odată ce și-au finalizat munca atribuită..

Cost/beneficiu

un canal tamponat mai mare de 1 nu oferă nicio garanție că un semnal trimis este primit vreodată. Există un beneficiu de a vă îndepărta de această garanție, care este latența redusă sau deloc în comunicarea dintre două goroutine. În scenariul Fan Out, există un spațiu tampon pentru fiecare angajat care va trimite un raport. În scenariul de cădere, tamponul este măsurat pentru capacitate și, dacă se atinge capacitatea, munca este scăzută, astfel încât lucrurile să poată continua să se miște.

în ambele opțiuni, această lipsă de garanție este ceva cu care trebuie să trăim, deoarece reducerea latenței este mai importantă. Cerința de latență de la zero la minim nu reprezintă o problemă pentru logica generală a sistemului.

semnal cu garanție întârziată de date – canal tamponat 1

când este necesar să știți dacă semnalul anterior care a fost trimis a fost primit înainte de a trimite un nou semnal, scenariul de așteptare pentru sarcini intră în joc.

Scenariul 1 – așteptați pentru sarcini

în acest scenariu aveți un nou angajat, dar acestea sunt de gând să facă mai mult decât o singură sarcină. Aveți de gând să le hrănească multe sarcini, una după alta. Cu toate acestea, trebuie să termine fiecare sarcină individuală înainte de a putea începe una nouă. Deoarece acestea pot lucra doar pe o singură sarcină la un moment dat ar putea exista probleme de latență între transferul de muncă. Dacă latența ar putea fi redusă fără a pierde garanția că angajatul lucrează la următoarea sarcină, ar putea ajuta.

acesta este locul în care un canal tamponat de 1 are beneficii. Dacă totul se desfășoară în ritmul așteptat între dvs. și angajat, niciunul dintre voi nu va trebui să aștepte celălalt. De fiecare dată când trimiteți o bucată de hârtie, tamponul este gol. De fiecare dată când angajatul dvs. ajunge la mai multă muncă, tamponul este plin. Este o simetrie perfectă a fluxului de lucru.

cea mai bună parte este aceasta. Dacă în orice moment încercați să trimiteți o bucată de hârtie și nu puteți, deoarece tamponul este plin, știți că angajatul dvs. are o problemă și vă opriți. Aici intervine garanția întârziată. Când tamponul este gol și efectuați trimiterea, aveți garanția că angajatul dvs. a luat ultima lucrare pe care ați trimis-o. Dacă efectuați trimiterea și nu puteți, aveți garanția că nu au făcut-o.

listare 9
https://play.golang.org/p/4pcuKCcAK3

01 func waitForTasks() {02 ch := make(chan string, 1)0304 go func() {05 for p := range ch {06 fmt.Println("employee : working :", p)07 }08 }()0910 const work = 1011 for w := 0; w < work; w++ {12 ch <- "paper"13 }1415 close(ch)16 }

pe linia 02 din lista 9, se creează un canal tamponat de dimensiunea 1 cu atributul că string datele vor fi trimise cu semnalul. Între liniile 04-08 un singur angajat este angajat să se ocupe de muncă. Un for range este utilizat pentru primirea canalului. De fiecare dată când se primește o bucată de hârtie, aceasta este procesată pe linia 06.

între liniile 10 până la 13 începeți să vă trimiteți sarcinile angajatului. Dacă angajatul dvs. poate rula cât de repede puteți trimite, latența dintre voi doi este redusă. Dar, cu fiecare trimite efectua cu succes, aveți garanția că ultima piesă de lucru pe care a prezentat este în curs de lucrat.

în cele din urmă pe linia 15, funcția încorporată close este apelată împotriva canalului. Acest lucru va semnala fără date angajatului pe care îl fac și liber să plece. Cu toate acestea, ultima lucrare pe care ați trimis-o va fi primită (spălată) înainte de terminarea for range.

semnal fără date-Context

în acest ultim scenariu veți vedea cum puteți anula o goroutine care rulează folosind o valoare Context din pachetul context. Toate acestea funcționează prin utilizarea unui canal Netamponat care este închis pentru a efectua un semnal fără date.

sunteți managerul pentru ultima dată și angajați un singur angajat pentru a face munca. De data aceasta nu sunteți dispus să așteptați o perioadă necunoscută de timp pentru ca angajatul să termine. Sunteți pe un termen discret și dacă angajatul nu termină la timp, nu sunteți dispus să așteptați.

listă 10
https://play.golang.org/p/6GQbN5Z7vC

01 func withTimeout() {02 duration := 50 * time.Millisecond0304 ctx, cancel := context.WithTimeout(context.Background(), duration)05 defer cancel()0607 ch := make(chan string, 1)0809 go func() {10 time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)11 ch <- "paper"12 }()1314 select {15 case p := <-ch:16 fmt.Println("work complete", p)1718 case <-ctx.Done():19 fmt.Println("moving on")20 }21 }

pe linia 02 din lista 10, se declară o valoare a duratei care reprezintă Cât timp va trebui angajatul să termine sarcina. Această valoare este utilizată pe linia 04 pentru a crea o valoare context.Context cu un timeout de 50 milisecunde. Funcția WithTimeout din pachetul context returnează o valoare Context și o funcție de anulare.

pachetul context creează un goroutine care va închide canalul Unbuffered asociat cu valoarea Context odată ce durata este îndeplinită. Sunteți responsabil pentru apelarea funcției cancel, indiferent de modul în care se întâmplă lucrurile. Acest lucru va curăța lucrurile care au fost create pentru Context. Este ok ca funcția cancel să fie apelată de mai multe ori.

pe linia 05, funcția cancel este amânată pentru a fi executată odată ce această funcție se termină. Pe linia 07 este creat un canal tamponat de 1, care va fi folosit de angajat pentru a vă trimite rezultatul muncii lor. Apoi, pe liniile 09 până la 12, angajatul este angajat și pus imediat la muncă. Nu ai nici o idee cât timp este de gând să ia angajat pentru a termina.

între liniile 14 până la 20 utilizați instrucțiunea select pentru a primi pe două canale. Primiți pe linia 15, așteptați ca angajatul să vă trimită rezultatul. Primiți pe linia 18, așteptați să vedeți dacă pachetul context va semnala că milisecundele 50 sunt în sus. Indiferent de semnalul pe care îl primiți mai întâi va fi cel procesat.

un aspect important al acestui algoritm este utilizarea canalului tamponat de 1. Dacă angajatul nu termină la timp, mergeți mai departe fără a da angajatului nicio notificare. Din perspectiva angajaților, aceștia vă vor trimite întotdeauna raportul pe linia 11 și sunt orbi dacă sunteți acolo sau nu pentru a-l primi. Dacă utilizați un canal Unbuffered, angajatul va bloca pentru totdeauna încercarea de a vă trimite raportul dacă mergeți mai departe. Acest lucru ar crea o scurgere goroutine. Deci, un canal tamponat de 1 este folosit pentru a preveni acest lucru.

concluzie

atributele semnalizării în jurul garanțiilor, starea canalului și trimiterea sunt importante pentru a cunoaște și înțelege atunci când se utilizează canale (sau concurență). Acestea vă vor ajuta să vă ghidați în implementarea celui mai bun comportament de care aveți nevoie pentru programele și algoritmii concurenți pe care îi scrieți. Acestea vă vor ajuta să găsiți bug-uri și să adulmecați codul potențial rău.

în această postare am împărtășit câteva exemple de programe care arată cum funcționează atributele semnalizării în diferite scenarii. Există excepții de la fiecare regulă, dar aceste modele sunt o bază bună pentru a începe.

examinați aceste contururi ca un rezumat al momentului și modului în care să gândiți și să utilizați în mod eficient canalele:

mecanica limbajului

  • folosiți canale pentru a orchestra și coordona goroutinele.
    • concentrați-vă pe atributele de semnalizare și nu pe partajarea datelor.
    • semnalizare cu date sau fără date.
    • pune la îndoială utilizarea lor pentru sincronizarea accesului la starea partajată.
      • există cazuri în care canalele pot fi mai simple pentru această întrebare, dar inițial.
  • canale Unbuffered:
    • primire se întâmplă înainte de trimitere.
    • beneficiu: 100% garanție semnalul a fost primit.
    • Cost: Latență necunoscut pe când semnalul va fi primit.
  • canale Buffered:
    • Trimite se întâmplă înainte de a primi.
    • beneficiu: reduceți latența de blocare între semnalizare.
    • Cost: nici o garanție atunci când semnalul a fost primit.
      • cu cât tamponul este mai mare, cu atât garanția este mai mică.
      • tampon de 1 poate da o întârziere trimite de garanție.
  • canale de închidere:
    • închidere se întâmplă înainte de a primi (cum ar fi tamponat).
    • semnalizare fără date.
    • Perfect pentru semnalizarea anulărilor și termenelor limită.
  • nil canale:
    • trimite și primi bloc.
    • opriți semnalizarea
    • Perfect pentru limitarea ratei sau opriri pe termen scurt.

filozofia de proiectare

  • dacă o trimitere dată pe un canal poate provoca blocarea goroutinei care trimite:
    • nu este permisă utilizarea unui canal tamponat mai mare de 1.
      • tampoanele mai mari de 1 trebuie să aibă motive/măsurători.
    • trebuie să știe ce se întâmplă când goroutine trimite blocuri.
  • dacă o trimitere dată pe un canal nu va cauza blocarea goroutine-ului de trimitere:
    • aveți numărul exact de tampoane pentru fiecare trimitere.
      • model de ieșire a ventilatorului
    • aveți tamponul măsurat pentru capacitatea maximă.
      • model de picătură
  • mai puțin este mai mult cu tampoane.
    • nu vă gândiți la performanță atunci când vă gândiți la tampoane.
    • tampoanele pot ajuta la reducerea latenței de blocare între semnalizare.
      • reducerea latenței de blocare către zero nu înseamnă neapărat un randament mai bun.
      • dacă un tampon de unul este oferindu-vă suficient de bun tranzitată apoi păstrați-l.
      • tampoane de întrebări care sunt mai mari decât unul și măsoară dimensiunea.
      • Găsiți cel mai mic tampon posibil care oferă un debit suficient de bun.

Lasă un răspuns

Adresa ta de email nu va fi publicată.