Go
совпадение
Поиск…
Вступление
В 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 в порядке выполнения:
- Объявление глобальной переменной. Сделать его глобальным - это самый простой способ сделать его видимым для всех функций и методов.
- Увеличение счетчика. Это должно быть сделано в главном goroutine, потому что нет гарантии, что недавно начатый goroutine будет выполняться до 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