Поиск…


Вступление

В Go параллелизм достигается за счет использования goroutines, а связь между goroutines обычно осуществляется с помощью каналов. Однако доступны другие средства синхронизации, такие как мьютексы и группы ожидания, и их следует использовать, когда они более удобны, чем каналы.

Синтаксис

  • go doWork () // запустить функцию doWork как goroutine
  • ch: = make (chan int) // объявляет новый канал типа int
  • ch <- 1 // отправка по каналу
  • value = <-ch // получение от канала

замечания

Горотины в Go похожи на потоки на других языках с точки зрения использования. Внутри Go создает несколько потоков (заданных GOMAXPROCS ), а затем планирует запуск gotoutines для потоков. Из-за этого дизайна механизмы параллелизма Go намного эффективнее, чем потоки с точки зрения использования памяти и времени инициализации.

Создание goroutines

Любая функция может быть вызвана как goroutine, префикс ее вызова ключевым словом 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

Обратите внимание, что возвращаемое значение функции игнорируется.

Привет, мир Горутин

одноканальный, один горутин, один пишут, один читается.

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)
}

Запустите его на детской площадке

Канал ch является небуферизованным или синхронным каналом .

time.Sleep здесь, чтобы проиллюстрировать функцию main() будет ждать на канале ch , что означает, что литерал функции, выполняемый как goroutine, имеет время для отправки значения через этот канал: оператор- <-ch блокирует выполнение main() . Если этого не произошло, горутин будет убит, когда main() выйдет и не успеет отправить его значение.

Ожидание гортанов

Программы Go заканчиваются, когда main функция заканчивается , поэтому обычной практикой является ожидание завершения всех goroutines. Общим решением для этого является использование объекта 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")
}

Запустите пример на игровой площадке

Использование WaitGroup в порядке выполнения:

  1. Объявление глобальной переменной. Сделать его глобальным - это самый простой способ сделать его видимым для всех функций и методов.
  2. Увеличение счетчика. Это должно быть сделано в главном goroutine, потому что нет гарантии, что недавно начатый goroutine будет выполняться до 4 из-за гарантии модели памяти.
  3. Уменьшение счетчика. Это должно быть сделано на выходе из горутины. Используя отложенный вызов, мы убеждаемся, что он будет вызываться всякий раз, когда функция заканчивается , независимо от того, как она заканчивается.
  4. Ожидание, когда счетчик достигнет 0. Это должно быть сделано в главном goroutine, чтобы программа не выходила, прежде чем все goroutines закончили.

* Параметры оцениваются перед запуском новой версии goroutine . Таким образом, необходимо явно определить их значения перед wg.Add(10) чтобы, возможно, панический код не увеличивал счетчик. Добавление 10 элементов в WaitGroup, поэтому он будет ждать 10 элементов до того, как wg.Wait вернет элемент управления обратно в main() goroutine. Здесь значение i определяется в цикле for.

Использование замыканий с гортанами в петле

Когда в цикле переменная цикла (val) в следующем примере представляет собой единственную переменную, которая меняет значение по мере прохождения цикла. Поэтому для того, чтобы фактически передать каждый val значений в goroutine, необходимо сделать следующее:

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

Если вы должны были просто выполнить func(val interface{}) { ... }() без передачи val, тогда значение val будет любым значением val, когда goroutines действительно выполняется.

Другой способ получить тот же эффект:

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

Странно выглядящий val := val создает новую переменную на каждой итерации, которая затем получает доступ к goroutine.

Остановка горти

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")
}

Пинг-понг с двумя гортанами

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)
    }
}

Запустите немного измененную версию этого кода в Go Playground



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow