dziękuję za subskrypcję

wprowadzenie

kiedy zacząłem pracować z kanałami go po raz pierwszy, popełniłem błąd, myśląc o kanałach jako strukturze danych. Widziałem kanały jako kolejkę, która zapewniała automatyczny zsynchronizowany dostęp między goroutines. To strukturalne zrozumienie spowodowało, że napisałem wiele złego i skomplikowanego kodu współbieżnego.

z czasem dowiedziałem się, że najlepiej zapomnieć o strukturze kanałów i skupić się na ich zachowaniu. Jeśli chodzi o kanały, myślę o jednym: o sygnalizacji. Kanał pozwala jednemu goroutine zasygnalizować innemu goroutine o konkretnym wydarzeniu. Sygnalizacja jest podstawą wszystkiego, co powinieneś robić z kanałami. Myślenie o kanałach jako mechanizmie sygnalizacyjnym pozwoli na pisanie lepszego kodu z dobrze zdefiniowanym i bardziej precyzyjnym zachowaniem.

aby zrozumieć, jak działa sygnalizacja, musimy zrozumieć jej trzy atrybuty:

  • gwarancja dostawy
  • Stan
  • z danymi lub bez danych

te trzy atrybuty współpracują ze sobą, tworząc filozofię projektowania wokół sygnalizacji. Po omówieniu tych atrybutów, przytoczę kilka przykładów kodu demonstrujących sygnalizację z zastosowanymi tymi atrybutami.

gwarancja dostawy

gwarancja dostawy opiera się na jednym pytaniu: „Czy potrzebuję gwarancji, że sygnał wysłany przez konkretną firmę został odebrany?”

innymi słowy, biorąc pod uwagę ten przykład w wykazie 1:

Lista 1

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

czy wysyłający goroutine potrzebuje gwarancji, że paper wysyłany przez kanał na linii 05 został odebrany przez goroutine na linii 02 przed przejściem dalej?

na podstawie odpowiedzi na to pytanie, będziesz wiedział, który z dwóch rodzajów kanałów użyć: Niebuforowany lub buforowany. Każdy kanał zapewnia inne zachowanie wokół gwarancji dostawy.

Rysunek 1 : Gwarancja dostawy

gwarancja dostawy

Gwarancje są ważne, a jeśli tak nie uważasz, mam mnóstwo rzeczy, które chcę Ci sprzedać. Oczywiście, próbuję żartować, ale nie denerwujesz się, kiedy nie masz gwarancji w życiu? Posiadanie silnego zrozumienia, czy potrzebujesz gwarancji, czy nie, ma kluczowe znaczenie podczas pisania równoległego oprogramowania. Jak będziemy kontynuować, nauczysz się, jak decydować.

Stan

zachowanie kanału jest bezpośrednio uzależnione od jego aktualnego stanu. Stan kanału może być zerowy, otwarty lub zamknięty.

Lista 2 poniżej pokazuje, jak zadeklarować lub umieścić kanał w każdym z tych trzech stanów.

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)

stan określa, jak zachowują się operacje wysyłania i odbierania.

sygnały są wysyłane i odbierane przez kanał.

Rysunek 2 : Stan

Stan

gdy kanał jest w stanie zerowym, każda próba wysłania lub odebrania na kanale zostanie zablokowana. Gdy kanał jest w stanie otwartym, sygnały mogą być wysyłane i odbierane. Gdy kanał jest umieszczony w stanie zamkniętym, sygnały nie mogą być już wysyłane, ale nadal można odbierać sygnały.

Stany te zapewnią różne zachowania potrzebne do różnych sytuacji, które napotkasz. Łącząc Państwo Z gwarancją dostawy, możesz zacząć analizować koszty / korzyści, które ponosisz w wyniku wyborów projektowych. W wielu przypadkach będziesz także w stanie szybko wykryć błędy po prostu czytając kod, ponieważ rozumiesz, jak kanał będzie się zachowywał.

z danymi i bez danych

ostatnim atrybutem sygnalizacji, który należy wziąć pod uwagę, jest to, czy trzeba sygnalizować z danymi, czy bez nich.

sygnujesz danymi wykonując wysyłanie na kanale.

3

01 ch <- "paper"

kiedy sygnał z danych, to zwykle dlatego, że:

  • goroutine zostaje poproszony o rozpoczęcie nowego zadania.
  • goroutine zgłasza wynik.

sygnujesz bez danych, zamykając kanał.

4

01 close(ch)

kiedy sygnujesz bez danych, to zwykle dlatego, że:

  • goroutine ma zaprzestać tego, co robią.
  • a goroutine donosi, że są zrobione bez rezultatu.
  • goroutine informuje, że zakończył przetwarzanie i zamknął.

istnieją wyjątki od tych zasad, ale są to główne przypadki użycia i te, na których skupimy się w tym poście. Uważam wyjątki od tych zasad za początkowy zapach kodu.

jedną z zalet sygnalizacji bez danych jest to, że jedna goroutine może sygnalizować wiele goroutine jednocześnie. Sygnalizacja z danymi jest zawsze wymianą 1 do 1 między goroutines.

Sygnalizacja za pomocą danych

gdy zamierzasz sygnalizować danymi, możesz wybrać trzy opcje konfiguracji kanału w zależności od rodzaju gwarancji, której potrzebujesz.

Rysunek 3 : Sygnalizacja danymi

opcje trzech kanałów są Niebuforowane, buforowane >1 lub buforowane =1.

  • gwarancja

    • Niebuforowany kanał daje gwarancję, że wysyłany sygnał został odebrany.
      • ponieważ odbiór sygnału następuje przed zakończeniem wysyłania sygnału.
  • Brak gwarancji

    • buforowany kanał o rozmiarze >1 nie daje gwarancji, że wysyłany sygnał został odebrany.
      • ponieważ wysłanie sygnału następuje przed zakończeniem odbioru sygnału.
  • opóźniona gwarancja

    • kanał buforowany o rozmiarze =1 daje opóźnioną gwarancję. Może zagwarantować, że poprzedni sygnał, który został wysłany, został odebrany.
      • ponieważ odbiór pierwszego sygnału następuje przed zakończeniem wysyłania drugiego sygnału.

rozmiar bufora nigdy nie może być liczbą losową, zawsze musi być obliczony dla jakiegoś dobrze zdefiniowanego ograniczenia. W informatyce nie ma nieskończoności, wszystko musi mieć jakieś dobrze zdefiniowane ograniczenia, czy to czas czy przestrzeń.

Sygnalizacja bez danych

Sygnalizacja bez danych jest zarezerwowana głównie do anulowania. Pozwala to jednej goroutine sygnalizować drugiej goroutine anulować to, co robią i przejść dalej. Anulowanie może być zaimplementowane zarówno za pomocą kanałów niebuforowanych, jak i buforowanych, ale użycie kanału buforowanego, gdy nie zostaną wysłane żadne dane, jest zapachem kodu.

Rysunek 4 : Sygnalizacja bez danych

wbudowana funkcja close służy do sygnalizowania bez danych. Jak wyjaśniono powyżej w sekcji Stan, nadal można odbierać sygnały na kanale, który jest zamknięty. W rzeczywistości każdy odbiór na zamkniętym kanale nie zostanie zablokowany, a operacja odbioru zawsze powraca.

w większości przypadków chcesz użyć pakietu standard library context do implementacji sygnalizacji bez danych. Pakiet context wykorzystuje Niebuforowany kanał pod spodem do sygnalizacji i wbudowaną funkcję close do sygnalizowania bez danych.

jeśli zdecydujesz się użyć własnego kanału do anulowania, a nie pakietu kontekstowego, Twój kanał powinien być typu chan struct{}. Jest to zerowy, idiomatyczny sposób wskazania kanału używanego tylko do sygnalizacji.

scenariusze

mając te atrybuty na miejscu, najlepszym sposobem na lepsze zrozumienie ich działania w praktyce jest przeprowadzenie serii scenariuszy kodu. Lubię myśleć o goroutines jako ludziach, gdy czytam i piszę kod oparty na kanałach. Ta wizualizacja naprawdę pomaga i użyję jej jako pomocy poniżej.

sygnał z gwarancją danych – kanały Niebuforowane

gdy musisz wiedzieć, że wysłany sygnał został odebrany, w grę wchodzą dwa scenariusze. Są to oczekiwanie na zadanie i oczekiwanie na wynik.

scenariusz 1 – poczekaj na zadanie

pomyśl o zostaniu menedżerem i zatrudnieniu nowego pracownika. W tym scenariuszu chcesz, aby Twój nowy pracownik wykonał zadanie, ale musi poczekać, aż będziesz gotowy. To dlatego, że trzeba podać im kawałek papieru, zanim zaczną.

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 }

na linii 02 w liście 5 tworzony jest Niebuforowany kanał z atrybutem, że string dane będą wysyłane z sygnałem. Następnie na linii 04, pracownik jest zatrudniony i kazał czekać na sygnał na linii 05 przed wykonaniem pracy. Linia 05 to kanał odbioru, powodujący zablokowanie pracownika w oczekiwaniu na kartkę papieru, którą wyślesz. Po otrzymaniu papieru przez pracownika, pracownik wykonuje pracę, a następnie jest gotowy i wolny do pracy.

Ty jako kierownik pracujesz równocześnie z nowym pracownikiem. Tak więc po zatrudnieniu pracownika na linii 04, znajdujesz się (na linii 12) robiąc to, co musisz zrobić, aby odblokować i zasygnalizować pracownikowi. Uwaga, nie było wiadomo, ile czasu zajmie przygotowanie tej kartki papieru, którą musisz wysłać.

w końcu jesteś gotowy, aby zasygnalizować pracownikowi. Na linii 14, wykonujesz sygnał z danymi, dane są tą kartką papieru. Ponieważ używany jest kanał Niebuforowany, otrzymujesz gwarancję, że pracownik otrzymał papier po zakończeniu operacji wysyłania. Odbiór odbywa się przed wysłaniem.

technicznie wszystko, co wiesz, to to, że pracownik ma Papier do czasu zakończenia operacji wysyłania kanału. Po obu operacjach na kanale scheduler może wykonać dowolną instrukcję. Następna linia kodu, która jest wykonywana przez Ciebie lub pracownika, jest nieokreślona. Oznacza to, że użycie instrukcji wydruku może oszukać Cię o kolejności rzeczy.

Scenariusz 2-oczekiwanie na wynik

w następnym scenariuszu wszystko się odwraca. Tym razem chcesz, aby Twój nowy pracownik wykonał zadanie natychmiast po zatrudnieniu i musisz poczekać na wynik swojej pracy. Musisz poczekać, ponieważ potrzebujesz papieru od nich, zanim będziesz mógł kontynuować.

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 }

na linii 02 w liście 6 tworzony jest Niebuforowany kanał z atrybutem, że string dane będą wysyłane z sygnałem. Następnie na linii 04, pracownik jest zatrudniony i jest natychmiast wprowadzony do pracy. Po zatrudnieniu pracownika na linii 04, znajdujesz się następny na linii 12 czekając na raport papierowy.

po zakończeniu pracy przez pracownika on line 05, wysyłają wynik do ciebie on line 07 wykonując kanał wysłać z danymi. Ponieważ jest to kanał Niebuforowany, odbiór następuje przed wysłaniem, a pracownik ma gwarancję, że otrzymałeś wynik. Gdy pracownik ma tę gwarancję, są gotowe i wolne. W tym scenariuszu nie masz pojęcia, ile czasu zajmie pracownikowi ukończenie zadania.

koszt/korzyść

Niebuforowany kanał zapewnia gwarancję otrzymania wysyłanego sygnału. To jest świetne, ale nic nie jest za darmo. Koszt tej gwarancji nie jest znany. W scenariuszu oczekiwania na zadanie pracownik nie ma pojęcia, ile czasu zajmie wysłanie artykułu. W scenariuszu oczekiwania na wynik nie masz pojęcia, ile czasu zajmie pracownikowi wysłanie ci tego wyniku.

w obu scenariuszach to nieznane opóźnienie jest czymś, z czym musimy żyć, ponieważ wymagana jest gwarancja. Logika nie działa bez tego gwarantowanego zachowania.

sygnał z danymi – bez gwarancji-kanały buforowane>1

kiedy nie musisz wiedzieć, że wysłany sygnał został odebrany, te dwa scenariusze wchodzą w grę: rozproszyć się i upuścić.

kanał buforowany ma dobrze zdefiniowaną przestrzeń, która może być używana do przechowywania wysyłanych danych. Jak więc decydujesz, ile miejsca potrzebujesz? Odpowiedz na te pytania:

  • Czy mam dobrze zdefiniowaną ilość pracy do wykonania?
    • ile jest pracy?
  • jeśli mój pracownik nie nadąża, czy mogę zrezygnować z jakiejkolwiek nowej pracy?
    • ile wybitnej pracy daje mi zdolność?
  • jaki poziom ryzyka jestem gotów zaakceptować, jeśli mój program nieoczekiwanie się zakończy?
    • wszystko, co czeka w buforze, zostanie utracone.

jeśli te pytania nie mają sensu dla zachowania, które modelujesz, to jest to zapach kodu, że używanie kanału buforowanego większego niż 1 jest prawdopodobnie błędne.

scenariusz 1 – Fan Out

wzór fan out pozwala rzucić dobrze określoną liczbę pracowników na problem, którzy pracują jednocześnie. Ponieważ masz jednego pracownika do każdego zadania, wiesz dokładnie, ile raportów otrzymasz. Możesz upewnić się, że w pudełku znajduje się odpowiednia ilość miejsca, aby otrzymać wszystkie te raporty. Dzięki temu pracownicy nie muszą czekać na przesłanie raportu. Muszą jednak wykonać kolejkę umieszczając raport w pudełku, jeśli dotrą do pudełka w tym samym czasie lub w pobliżu.

wyobraź sobie, że znowu jesteś menedżerem, ale tym razem zatrudniasz zespół pracowników. Masz indywidualne zadanie, które ma wykonać każdy pracownik. Gdy każdy pracownik kończy swoje zadanie, musi dostarczyć Ci raport w formie papierowej, który należy umieścić w pudełku na biurku.

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 }

na linii 03 w liście 7 tworzony jest kanał buforowany z atrybutem, że dane string będą wysyłane z sygnałem. Tym razem kanał jest tworzony z 20 buforami dzięki zmiennej emps zadeklarowanej na linii 02.

między liniami od 05 do 10 zatrudnionych jest 20 pracowników i natychmiast zabierają się do pracy. Nie masz pojęcia, jak długo każdy pracownik zajmie na linii 07. Następnie na linii 08 pracownicy wysyłają raport papierowy, ale tym razem wysyłanie nie blokuje oczekiwania na odbiór. Ponieważ w pudełku jest miejsce dla każdego pracownika, wyślij na kanale konkuruje tylko z innymi pracownikami, którzy mogą chcieć wysłać swój raport w tym samym czasie lub w pobliżu.

Kod między wierszami od 12 do 16 jest wszystkim. W tym miejscu czeka się na wszystkich 20 pracowników, którzy zakończą pracę i wyślą raport. Na linii 12 jesteś w pętli, a na linii 13 jesteś zablokowany w kanale odbieraj czekając na Twoje raporty. Po otrzymaniu raportu Raport jest drukowany w linii 14, a lokalna zmienna licznika jest zmniejszana, aby wskazać pracownika.

Scenariusz 2 – Spadek

wzór spadku pozwala na odrzucenie pracy, gdy pracownik(pracownicy) są w pełni sprawni. Ma to tę zaletę, że nadal przyjmuje pracę od swoich klientów i nigdy nie stosuje presji zwrotnej lub opóźnienia w akceptacji tej pracy. Kluczem tutaj jest wiedza, kiedy jesteś naprawdę w stanie, więc nie Pod lub nad zobowiązać się do ilości pracy, którą będziesz próbował zrobić. Zazwyczaj testy integracyjne lub metryki są tym, czego potrzebujesz, aby pomóc zidentyfikować ten numer.

wyobraź sobie, że jesteś ponownie menedżerem i zatrudniasz jednego pracownika do pracy. Masz indywidualne zadanie, które ma wykonać pracownik. Gdy pracownik kończy swoje zadanie, nie dbasz o to, aby wiedzieć, że zostały wykonane. Ważne jest tylko to, czy możesz lub nie możesz umieścić nowej pracy w pudełku. Jeśli nie możesz wykonać wysłania, wiesz, że Twoja skrzynka jest pełna, a pracownik jest w pełni sprawny. W tym momencie nowa praca musi zostać odrzucona, aby rzeczy mogły się poruszać.

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 }

na linii 03 w liście 8 tworzony jest kanał buforowany z atrybutem, że dane string będą wysyłane z sygnałem. Tym razem kanał jest tworzony z 5 buforami dzięki stałej cap zadeklarowanej na linii 02.

między liniami 05 A 09 do obsługi prac zatrudniony jest jeden pracownik. for range jest używany do odbioru kanału. Za każdym razem, gdy otrzymujemy kartkę papieru, jest ona przetwarzana na linii 07.

między wierszami od 11 do 19 próbujesz wysłać pracownikowi 20 kartek papieru. Tym razem polecenie select jest używane do wykonania polecenia send wewnątrz pierwszego case w linii 14. Ponieważ klauzula default jest używana wewnątrz select linii 16, jeśli send ma się zablokować, ponieważ nie ma więcej miejsca w buforze, wysyłanie jest porzucane przez linię 17.

wreszcie na linii 21, wbudowana funkcja close jest wywoływana przeciwko kanałowi. To będzie sygnał bez danych do pracownika, że są one wykonane i wolne, aby przejść po zakończeniu ich przypisanej pracy..

koszt/korzyść

kanał buforowany większy niż 1 nie gwarantuje, że wysyłany sygnał zostanie kiedykolwiek odebrany. Istnieje korzyść z odejścia od tej gwarancji, którą jest zmniejszone opóźnienie lub brak opóźnienia w komunikacji między dwoma goroutines. W scenariuszu Fan Out dla każdego pracownika, który wyśle raport, znajduje się przestrzeń buforowa. W scenariuszu Drop bufor jest mierzony dla pojemności, a jeśli pojemność zostanie osiągnięta, praca jest usuwana, aby rzeczy mogły się poruszać.

w obu opcjach ten brak gwarancji jest czymś, z czym musimy żyć, ponieważ redukcja opóźnień jest ważniejsza. Wymóg opóźnienia od zera do minimum nie stanowi problemu dla ogólnej logiki systemu.

sygnał z opóźnioną gwarancją – buforowany Kanał 1

gdy konieczne jest sprawdzenie, czy poprzedni sygnał został odebrany przed wysłaniem nowego sygnału, scenariusz oczekiwania na zadania wchodzi w grę.

scenariusz 1 – oczekiwanie na zadania

w tym scenariuszu masz nowego pracownika, ale on zrobi więcej niż jedno zadanie. Będziesz karmić je wiele zadań, jeden po drugim. Muszą jednak ukończyć każde zadanie, zanim będą mogli rozpocząć nowe. Ponieważ mogą pracować tylko nad jednym zadaniem na raz, mogą wystąpić problemy z opóźnieniem między przekazaniem pracy. Jeśli opóźnienie może zostać zmniejszone bez utraty gwarancji, że pracownik pracuje nad następnym zadaniem, może to pomóc.

to jest, gdzie kanał buforowany 1 ma korzyści. Jeśli wszystko działa w oczekiwanym tempie między tobą a pracownikiem, żadne z was nie będzie musiało czekać na drugiego. Za każdym razem, gdy wysyłasz kartkę papieru, bufor jest pusty. Za każdym razem, gdy twój pracownik sięga po więcej pracy, bufor jest pełny. Jest to doskonała symetria przepływu pracy.

najlepsze jest to. Jeśli w dowolnym momencie spróbujesz wysłać kartkę papieru, a nie możesz, ponieważ bufor jest pełny, wiesz, że twój pracownik ma problem i przestajesz. Tu pojawia się opóźniona gwarancja. Gdy bufor jest pusty i wykonujesz wysyłkę, masz gwarancję, że twój pracownik przyjął ostatnią wysłaną pracę. Jeśli wykonasz wysyłkę, a nie możesz, masz gwarancję, że nie masz.

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 }

na linii 02 w liście 9 tworzony jest buforowany kanał o rozmiarze 1 z atrybutem, że string dane będą wysyłane z sygnałem. Pomiędzy liniami od 04 do 08 jeden pracownik jest zatrudniony do obsługi pracy. for range jest używany do odbioru kanału. Za każdym razem, gdy otrzymujemy kartkę papieru, jest ona przetwarzana na linii 06.

między wierszami od 10 do 13 zaczynasz wysyłać swoje zadania do pracownika. Jeśli twój pracownik może działać tak szybko, jak możesz wysłać, opóźnienie między wami zostanie zmniejszone. Ale z każdym wysłaniem, które wykonasz pomyślnie, masz gwarancję, że ostatnia przesłana praca jest opracowywana.

wreszcie na linii 15, wbudowana funkcja close jest wywoływana przeciwko kanałowi. To zasygnalizuje bez danych pracownikowi, że są gotowe i wolne. Jednak ostatnia przesłana praca zostanie odebrana (spłukana) przed zakończeniem działania for range.

sygnał bez kontekstu danych

w tym ostatnim scenariuszu zobaczysz, jak możesz anulować uruchomioną goroutine używając wartości Context z pakietu context. To wszystko działa poprzez wykorzystanie Niebuforowanego kanału, który jest zamknięty, aby wykonać sygnał bez danych.

jesteś menedżerem po raz ostatni i zatrudniasz jednego pracownika do pracy. Tym razem nie jesteś skłonny czekać na jakąś nieznaną ilość czasu, aby pracownik skończył. Jesteś w dyskretnym terminie, a jeśli pracownik nie skończy na czas, nie chcesz czekać.

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 }

w linii 02 na liście 10 deklarowana jest wartość czasu trwania, która określa, jak długo pracownik będzie musiał zakończyć zadanie. Ta wartość jest używana w linii 04 do wytworzenia context.Context wartości z timeoutem 50 milisekund. Funkcja WithTimeout z pakietu context Zwraca wartość Context i funkcję anulowania.

pakiet context tworzy goroutine, który zamknie Niebuforowany kanał związany z wartością Context po osiągnięciu czasu trwania. Jesteś odpowiedzialny za wywołanie funkcji cancel niezależnie od tego, jak sprawy się potoczą. Spowoduje to wyczyszczenie rzeczy, które zostały utworzone dla Context. Funkcja cancel może być wywoływana więcej niż raz.

w linii 05 funkcja cancel jest odroczona do wykonania po zakończeniu tej funkcji. Na linii 07 tworzony jest buforowany kanał 1, który będzie używany przez pracownika, aby wysłać Ci wynik swojej pracy. Następnie na liniach od 09 do 12 pracownik jest zatrudniony i natychmiast oddany do pracy. Nie masz pojęcia, ile czasu zajmie pracownikowi Ukończenie.

między wierszami od 14 do 20 używasz instrukcji select, aby odbierać na dwóch kanałach. Odbierz na linii 15, czekasz, aż pracownik wyśle Ci swój wynik. Odbiór na linii 18, czekasz, aby zobaczyć, czy pakiet context ma sygnalizować, że 50 milisekund jest up. Niezależnie od tego, który sygnał otrzymasz jako pierwszy, zostanie przetworzony.

ważnym aspektem tego algorytmu jest użycie buforowanego kanału 1. Jeśli pracownik nie skończy na czas, idziesz dalej bez powiadomienia pracownika. Z punktu widzenia pracownika, zawsze wyśle Ci raport na linii 11 i są ślepi, jeśli jesteś tam lub nie, aby go otrzymać. Jeśli korzystasz z kanału Niebuforowanego, pracownik zablokuje na zawsze próbę wysłania Ci raportu, jeśli przejdziesz dalej. To spowodowałoby wyciek goroutine ’ a. Więc buforowany kanał 1 jest używany, aby temu zapobiec.

podsumowanie

atrybuty sygnalizacji wokół gwarancji, stanu kanału i wysyłania są ważne do poznania i zrozumienia podczas korzystania z kanałów (lub współbieżności). Pomogą Ci w wdrożeniu najlepszego zachowania, którego potrzebujesz dla jednoczesnych programów i algorytmów, które piszesz. Pomogą Ci znaleźć błędy i wywęszyć potencjalnie zły kod.

w tym poście udostępniłem kilka przykładowych programów, które pokazują, jak działają atrybuty sygnalizacji w różnych scenariuszach. Istnieją wyjątki od każdej reguły, ale te wzorce są dobrym fundamentem do rozpoczęcia.

przejrzyj te kontury jako podsumowanie, kiedy i jak skutecznie myśleć o kanałach i korzystać z nich:

Mechanika języka

  • użyj kanałów do orkiestracji i koordynowania goroutines.
    • skup się na atrybutach sygnalizacji, a nie na udostępnianiu danych.
    • Sygnalizacja z danymi lub bez danych.
    • kwestionuj ich użycie do synchronizacji dostępu do stanu współdzielonego.
      • istnieją przypadki, w których kanały mogą być prostsze do tego, ale początkowo pytanie.
  • kanały Niebuforowane:
    • odbiór odbywa się przed wysłaniem.
    • korzyść: 100% gwarancja odebrania sygnału.
    • koszt: Nieznane Opóźnienie Na kiedy sygnał zostanie odebrany.
  • kanały buforowane:
    • Wyślij dzieje się przed odbiorem.
    • korzyści: zmniejszenie opóźnienia blokowania między sygnałami.
    • koszt: brak gwarancji, gdy sygnał został odebrany.
      • im większy bufor, tym mniejsza gwarancja.
      • Bufor 1 może dać jedno opóźnione wysłanie gwarancji.
  • zamykanie kanałów:
    • zamykanie odbywa się przed odbiorem (jak buforowane).
    • Sygnalizacja bez danych.
    • idealny do sygnalizowania odwołań i terminów.
  • kanały zerowe:
    • blok wysyłania i odbierania.
    • Wyłącz sygnalizację
    • idealny do ograniczania prędkości lub krótkotrwałych przestojów.

filozofia projektowania

  • jeśli którekolwiek dane wysłanie na kanale może spowodować zablokowanie goroutine ’ a wysyłającego:
    • nie wolno używać kanału buforowanego większego niż 1.
      • bufory większe niż 1 muszą mieć powód / pomiary.
    • musi wiedzieć, co się dzieje, gdy wysłanie goroutine blokuje.
  • jeĹ „li ktăłreĺ” dane wysyĹ 'anie na kanale nie spowoduje zablokowania goroutine 'a wysyĹ’ ajÄ … cego:
    • masz dokĹ 'adnÄ … liczbÄ ™ buforăłw dla kaĹźdego wysyĹ’ ania.
      • Fan Out pattern
    • masz zmierzony bufor dla maksymalnej pojemności.
      • wzór kropli
  • mniej znaczy więcej z buforami.
    • nie myśl o wydajności, gdy myślisz o buforach.
    • bufory mogą pomóc zmniejszyć opóźnienie blokowania między sygnałami.
      • zmniejszenie opóźnienia blokowania do zera niekoniecznie oznacza lepszą przepustowość.
      • jeśli jeden bufor daje wystarczająco dobrą przepustowość, zachowaj go.
      • bufory pytań, które są większe niż jeden i mierzą rozmiar.
      • Znajdź najmniejszy możliwy bufor, który zapewnia wystarczająco dobrą przepustowość.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.