Ответ
Модель конкурентности в Go основана на концепции "Communicating Sequential Processes" (CSP) Тони Хоара. Основной принцип: "Не обменивайтесь данными через общую память; обменивайтесь памятью через каналы".
Реализация построена на двух ключевых элементах:
Горутины (Goroutines)
- Это легковесные потоки, управляемые планировщиком Go (Go Scheduler), а не напрямую операционной системой.
- Они намного дешевле системных потоков. Начальный размер стека горутины всего ~2 КБ, в то время как у системного потока — мегабайты. Это позволяет создавать сотни тысяч и даже миллионы горутин одновременно.
- Запускаются с помощью ключевого слова
go
перед вызовом функции:go myFunction()
.
Каналы (Channels)
- Это типизированные "трубы", через которые горутины могут безопасно обмениваться данными, не прибегая к мьютексам и другим примитивам синхронизации.
- Обеспечивают синхронизацию: отправка в канал блокируется, пока кто-то не будет готов принять данные, и наоборот (для небуферизированных каналов).
- Бывают небуферизированные (
make(chan int)
) и буферизированные (make(chan int, 10)
).
Пример: Worker Pool
В этом примере мы создаем пул воркеров (горутин), которые обрабатывают задачи из канала jobs
и отправляют результаты в канал results
. sync.WaitGroup
используется для ожидания завершения всех горутин.
package main
import (
"fmt"
"sync"
)
// worker - это горутина, которая читает из канала jobs и пишет в results
func worker(id int, wg *sync.WaitGroup, jobs <-chan int, results chan<- int) {
defer wg.Done() // Сообщаем WaitGroup, что горутина завершила работу
for j := range jobs {
fmt.Printf("worker %d started job %dn", id, j)
results <- j * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// Запускаем 3 воркера
for w := 1; w <= 3; w++ {
wg.Add(1) // Увеличиваем счетчик WaitGroup
go worker(w, &wg, jobs, results)
}
// Отправляем 5 задач в канал jobs
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs) // Закрываем канал, чтобы горутины завершили цикл for-range
// Ждем, пока все воркеры завершат свою работу
wg.Wait()
// Собираем все результаты
// Важно: этот код должен выполняться после wg.Wait(),
// чтобы гарантировать, что все результаты уже отправлены.
close(results)
for r := range results {
fmt.Println("Result:", r)
}
}
Дополнительные инструменты:
select
: Позволяет горутине ждать операции сразу в нескольких каналах.- Пакет
sync
: Предоставляет традиционные примитивы синхронизации, такие какMutex
,RWMutex
,WaitGroup
,Cond
иOnce
, когда модель каналов не подходит.