Łapanie wartości zwracanych z gorutynów

83

Poniższy kod powoduje błąd kompilacji z komunikatem „nieoczekiwane zakończenie”:

x := go doSomething(arg)

func doSomething(arg int) int{
    ...
    return my_int_value
}

Wiem, że mogę pobrać wartość zwracaną, jeśli wywołam funkcję normalnie, bez użycia goroutine. Lub mogę użyć kanałów itp.

Moje pytanie brzmi: dlaczego nie można pobrać takiej wartości zwracanej z goroutine.

Nerw
źródło
7
możesz użyć kanału, aby go zwrócić
rogerdpack
dlaczego pozwala na zwracanie wartości dla goroutine
srinath samala
1
@rogerdpack, który wymaga zmiany interfejsu API dowolnej używanej funkcji. więc możesz potrzebować funkcji opakowującej, jeśli nie jest ona twoja
David Callanan,

Odpowiedzi:

67

Ścisła odpowiedź brzmi, że możesz to zrobić. To chyba nie jest dobry pomysł. Oto kod, który by to zrobił:

var x int
go func() {
    x = doSomething()
}()

Spowoduje to powstanie nowej gorutyny, która obliczy, doSomething()a następnie przypisze wynik x. Problem w tym, że jak zamierzasz wykorzystać xoryginalną gorutynę? Prawdopodobnie chcesz się upewnić, że spawnowana gorutyna jest z nią zrobiona, abyś nie miał warunków rasy. Ale jeśli chcesz to zrobić, potrzebujesz sposobu na komunikację z gorutynem, a jeśli masz na to sposób, dlaczego nie użyć go po prostu do odesłania wartości?

joshlf
źródło
6
Możesz dodać WaitGroup, aby upewnić się, że skończyłeś i poczekać na to. Ale jak powiedziałeś, po prostu nie jest to sposób na zrobienie tego, kanał jest.
Not_a_Golfer,
1
To nie jest return, to assignment
Nidhin David
95

Dlaczego nie można pobrać wartości zwracanej z gorutyny przypisującej ją do zmiennej?

Uruchamianie goroutine (asynchronicznie) i pobieranie wartości zwracanej z funkcji są zasadniczo sprzecznymi akcjami. Kiedy mówisz, że gomasz na myśli „rób to asynchronicznie” lub nawet prościej: „Dalej! Nie czekaj na zakończenie wykonywania funkcji”. Ale kiedy przypiszesz wartość zwracaną przez funkcję do zmiennej, spodziewasz się, że ta wartość będzie znajdować się w zmiennej. Więc kiedy to zrobisz, x := go doSomething(arg)mówisz: "Dalej, nie czekaj na funkcję! Czekaj-czekaj-czekaj! Potrzebuję, aby zwrócona wartość była dostępna w xvar bezpośrednio w następnym wierszu poniżej!"

Kanały

Najbardziej naturalnym sposobem uzyskania wartości z gorutyny są kanały. Kanały to rury, które łączą równoległe gorutyny. Możesz wysyłać wartości do kanałów z jednej gorutyny i odbierać te wartości do innej gorutyny lub w funkcji synchronicznej. Możesz łatwo uzyskać wartość z gorutyny, która nie przerywa współbieżności, używając select:

func main() {

    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        time.Sleep(time.Second * 1)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(time.Second * 2)
        c2 <- "two"
    }()

    for i := 0; i < 2; i++ {
        // Await both of these values
        // simultaneously, printing each one as it arrives.
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        } 
    }
}

Przykład pochodzi z Go By Example

CSP i przekazywanie wiadomości

Go jest w dużej mierze oparty na teorii CSP . Naiwny opis z powyższego mógłby być precyzyjnie zarysowany w kontekście CSP (chociaż uważam, że nie wchodzi w zakres pytania). Zdecydowanie zalecam zapoznanie się z teorią CSP, przynajmniej dlatego, że jest to RAD. Te krótkie cytaty dają kierunek myślenia:

Jak sama nazwa wskazuje, CSP umożliwia opis systemów w kategoriach procesów składowych, które działają niezależnie i współdziałają ze sobą wyłącznie poprzez komunikację z przekazywaniem wiadomości .

W informatyce przekazywanie wiadomości wysyła wiadomość do procesu i opiera się na procesie oraz infrastrukturze pomocniczej w celu wybrania i wywołania właściwego kodu do uruchomienia. Przekazywanie komunikatów różni się od konwencjonalnego programowania, w którym proces, podprogram lub funkcja jest bezpośrednio wywoływana przez nazwę.

I159
źródło
9

Idea gosłowa kluczowego polega na tym, że uruchamiasz funkcję doSomething asynchronicznie i kontynuujesz bieżącą gorutynę bez czekania na wynik, podobnie jak wykonywanie polecenia w powłoce Bash ze znakiem „&” po nim. Jeśli chcesz

x := doSomething(arg)
// Now do something with x

wtedy musisz zablokować bieżący goroutine, dopóki nie zakończy się funkcja doSomething. Dlaczego więc nie zadzwonić po prostu do funkcji doSomething w obecnym gorutynie? Istnieją inne opcje (na przykład doSomething może wysłać wynik do kanału, z którego bieżąca goroutine otrzymuje wartości), ale po prostu wywołanie funkcji doSomething i przypisanie wyniku do zmiennej jest oczywiście prostsze.

MatrixFrog
źródło
0

To wybór projektu przez twórców Go. Jest dużo abstrakcji / API do reprezentowania wartości asynchronicznych operacji I / O - promise, future, async/await, callback, observable, itd. Te abstrakcje / API są nieodłącznie związane z jednostką planowania - współprogram - a te abstrakcje / API dyktować jak współprogram ( a dokładniej wartości powrotu asynchroniczny I / o reprezentowane przez nie) może składać się .

Go wybrał przekazywanie komunikatów (czyli kanały ) jako abstrakcję / API do reprezentowania wartości zwracanej przez asynchroniczne operacje we / wy. Oczywiście gorutyny i kanały zapewniają komponowalne narzędzie do implementacji asynchronicznych operacji we / wy.

Bharat Khatri
źródło