Szukaj…


Wprowadzenie

W Go współbieżność osiąga się poprzez użycie goroutyn, a komunikacja między goroutynami odbywa się zwykle za pomocą kanałów. Dostępne są jednak inne środki synchronizacji, takie jak muteksy i grupy oczekiwania, i należy ich używać, gdy są wygodniejsze niż kanały.

Składnia

  • go doWork () // uruchom funkcję doWork jako goroutine
  • ch: = make (chan int) // zadeklaruj nowy kanał typu int
  • ch <- 1 // wysyłanie na kanał
  • wartość = <-ch // odbieranie z kanału

Uwagi

Goroutines in Go są podobne do wątków w innych językach pod względem użytkowania. Wewnętrznie Go tworzy wiele wątków (określonych przez GOMAXPROCS ), a następnie planuje uruchomienie goroutine na wątkach. Z powodu tej konstrukcji mechanizmy współbieżności Go są znacznie wydajniejsze niż wątki pod względem zużycia pamięci i czasu inicjalizacji.

Tworzenie goroutines

Dowolną funkcję można wywołać jako goroutine, poprzedzając ją wywołaniem słowem kluczowym go :

func DoMultiply(x,y int) {
    // Simulate some hard work
    time.Sleep(time.Second * 1)
    fmt.Printf("Result: %d\n", x * y)
}

go DoMultiply(1,2) // first execution, non-blocking
go DoMultiply(3,4) // second execution, also non-blocking

// Results are printed after a single second only, 
// not 2 seconds because they execute concurrently:
// Result: 2
// Result: 12

Zauważ, że zwracana wartość funkcji jest ignorowana.

Hello World Goroutine

pojedynczy kanał, jeden goroutine, jeden zapis, jeden odczyt.

package main

import "fmt"
import "time"

func main() {
    // create new channel of type string
    ch := make(chan string)

    // start new anonymous goroutine
    go func() {
        time.Sleep(time.Second)
        // send "Hello World" to channel
        ch <- "Hello World"
    }()
    // read from channel
    msg, ok := <-ch
    fmt.Printf("msg='%s', ok='%v'\n", msg, ok)
}

Uruchom na placu zabaw

Kanał ch jest niebuforowanym lub synchronicznym kanałem .

time.Sleep jest tutaj, aby zilustrować funkcję main() która zaczeka na kanale ch , co oznacza, że funkcja dosłownie wykonana jako goroutine ma czas na przesłanie wartości przez ten kanał: operator odbierania <-ch zablokuje wykonanie main() . Gdyby tak nie było, goroutine zostałby zabity, gdy main() wyjdzie, i nie miałby czasu na przesłanie swojej wartości.

Czekam na goroutiny

Programy Go kończą się, gdy kończy się main funkcja , dlatego powszechną praktyką jest czekanie na zakończenie wszystkich goroutyn. Częstym rozwiązaniem tego problemu jest użycie obiektu sync.WaitGroup .

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup // 1

func routine(i int) {
    defer wg.Done() // 3
    fmt.Printf("routine %v finished\n", i)
}

func main() {
    wg.Add(10) // 2
    for i := 0; i < 10; i++ {
        go routine(i) // *
    }
    wg.Wait() // 4
    fmt.Println("main finished")
}

Uruchom przykład na placu zabaw

Użycie WaitGroup w kolejności wykonania:

  1. Deklaracja zmiennej globalnej. Uczynienie go globalnym jest najłatwiejszym sposobem, aby był widoczny dla wszystkich funkcji i metod.
  2. Zwiększenie licznika. Należy to zrobić w głównym goroutine, ponieważ nie ma gwarancji, że nowo uruchomiony goroutine wykona się przed 4 ze względu na gwarancje modelu pamięci.
  3. Zmniejszenie licznika. Trzeba to zrobić przy wyjściu z goroutine. Używając odroczonego wywołania, upewniamy się, że będzie ono wywoływane za każdym razem, gdy funkcja się zakończy , bez względu na to, jak się zakończy.
  4. Oczekiwanie, aż licznik osiągnie 0. Trzeba to zrobić w głównym goroutine, aby zapobiec wyjściu programu przed zakończeniem działania wszystkich goroutine.

* Parametry są oceniane przed rozpoczęciem nowego goroutine . Dlatego konieczne jest jawne zdefiniowanie ich wartości przed wg.Add(10) , aby ewentualnie kod panikujący nie zwiększył licznika. Dodanie 10 elementów do WaitGroup, więc będzie czekać na 10 elementów przed wg.Wait zwraca kontrolę z powrotem do main() goroutine. Tutaj wartość i jest zdefiniowana w pętli for.

Używanie zamknięć z goroutynami w pętli

W pętli zmienna pętli (val) w poniższym przykładzie jest pojedynczą zmienną, która zmienia wartość, gdy przechodzi przez pętlę. Dlatego należy wykonać następujące czynności, aby faktycznie przekazać każdą wartość wartości do goroutine:

for val := range values {
    go func(val interface{}) {
        fmt.Println(val)
    }(val)
}

Jeśli miałbyś to zrobić, po prostu zrób func(val interface{}) { ... }() bez przekazywania wartości val, wówczas wartość val będzie równa wartości val, gdy faktycznie uruchomią się goroutines.

Innym sposobem na uzyskanie tego samego efektu jest:

for val := range values {
    val := val
    go func() {
        fmt.Println(val)
    }()
}

Dziwnie wyglądająca wartość val := val tworzy nową zmienną w każdej iteracji, do której następnie ma dostęp goroutine.

Zatrzymywanie goroutines

package main

import (
    "log"
    "sync"
    "time"
)

func main() {
    // The WaitGroup lets the main goroutine wait for all other goroutines
    // to terminate. However, this is no implicit in Go. The WaitGroup must
    // be explicitely incremented prior to the execution of any goroutine 
    // (i.e. before the `go` keyword) and it must be decremented by calling
    // wg.Done() at the end of every goroutine (typically via the `defer` keyword). 
    wg := sync.WaitGroup{}

    // The stop channel is an unbuffered channel that is closed when the main
    // thread wants all other goroutines to terminate (there is no way to 
    // interrupt another goroutine in Go). Each goroutine must multiplex its
    // work with the stop channel to guarantee liveness.
    stopCh := make(chan struct{})


    for i := 0; i < 5; i++ {
        // It is important that the WaitGroup is incremented before we start
        // the goroutine (and not within the goroutine) because the scheduler
        // makes no guarantee that the goroutine starts execution prior to 
        // the main goroutine calling wg.Wait().
        wg.Add(1)
        go func(i int, stopCh <-chan struct{}) {
            // The defer keyword guarantees that the WaitGroup count is 
            // decremented when the goroutine exits.
            defer wg.Done()

            log.Printf("started goroutine %d", i)

            select {
            // Since we never send empty structs on this channel we can 
            // take the return of a receive on the channel to mean that the
            // channel has been closed (recall that receive never blocks on
            // closed channels).   
            case <-stopCh:
                log.Printf("stopped goroutine %d", i)
            }
        }(i, stopCh)
    }

    time.Sleep(time.Second * 5)
    close(stopCh)
    log.Printf("stopping goroutines")
    wg.Wait()
    log.Printf("all goroutines stopped")
}

Ping pong z dwoma goroutinami

package main

import (
    "fmt"
    "time"
)

// The pinger prints a ping and waits for a pong
func pinger(pinger <-chan int, ponger chan<- int) {
    for {
        <-pinger
        fmt.Println("ping")
        time.Sleep(time.Second)
        ponger <- 1
    }
}

// The ponger prints a pong and waits for a ping
func ponger(pinger chan<- int, ponger <-chan int) {
    for {
        <-ponger
        fmt.Println("pong")
        time.Sleep(time.Second)
        pinger <- 1
    }
}

func main() {
    ping := make(chan int)
    pong := make(chan int)

    go pinger(ping, pong)
    go ponger(ping, pong)

    // The main goroutine starts the ping/pong by sending into the ping channel
    ping <- 1

    for {
        // Block the main thread until an interrupt
        time.Sleep(time.Second)
    }
}

Uruchom nieco zmodyfikowaną wersję tego kodu w Go Playground



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow