Несколько задач в работе одновременно
В обычном последовательном коде следующая операция начинается после предыдущей. При конкурентном выполнении несколько задач продвигаются независимо: планировщик Go распределяет горутины по доступным потокам и процессорам. Независимые вычисления могут выполняться параллельно, а ожидание внешней операции может чередоваться с работой других задач.
Конкурентность не задаёт порядок, в котором задачи завершатся. Поэтому программа не должна полагаться на то, какая горутина первой напечатает строку или изменит общее значение. Если результат должен быть воспроизводимым, собери ответы, а затем выведи их в определённом порядке.
Пример
package main
import (
"fmt"
"sort"
"sync"
)
type taskResult struct {
position int
value int
}
func main() {
numbers := []int{8, 3, 6, 2}
results := make(chan taskResult, len(numbers))
var workers sync.WaitGroup
for position, number := range numbers {
workers.Add(1)
go func(position, number int) {
defer workers.Done()
results <- taskResult{position: position, value: number * number}
}(position, number)
}
go func() {
workers.Wait()
close(results)
}()
completed := make([]taskResult, 0, len(numbers))
for result := range results {
completed = append(completed, result)
}
sort.Slice(completed, func(i, j int) bool {
return completed[i].position < completed[j].position
})
for _, result := range completed {
fmt.Printf("позиция %d: %d\n", result.position, result.value)
}
}
Каждый расчёт независим, поэтому его можно начать отдельной горутиной. Буфер результатов вмещает по одному ответу на задачу: горутина может отправить ответ, пока основная функция собирает остальные. Группа ожидания сообщает координатору, когда все отправители завершили работу; только координатор закрывает канал после ожидания.
Канал может принести ответы в любом порядке. Поле position сохраняет исходное место задачи, а сортировка перед печатью делает вывод стабильным. Одновременное выполнение не гарантирует ускорение для каждой программы: запуск и координация тоже имеют стоимость.