Ответ
В основе конкурентности в Go лежит философия: "Не обменивайтесь данными через общую память; вместо этого, делитесь памятью путем обмена данными". Эта модель реализована с помощью горутин и каналов.
Горутины (Goroutines)
Это легковесные потоки выполнения, управляемые средой выполнения Go (Go runtime), а не операционной системой.
- Создание: Запускаются с помощью ключевого слова
go
перед вызовом функции:go myFunction()
. - Стоимость: Очень "дешевые". Начальный размер стека всего ~2 КБ, который может динамически расти. Можно легко запустить сотни тысяч горутин.
- Планировщик: Go использует планировщик M:N, который распределяет M горутин на N потоков ОС. Это позволяет эффективно использовать многоядерные процессоры.
Каналы (Channels)
Это типизированные "трубы", через которые горутины могут безопасно обмениваться данными, избегая гонок данных.
- Создание:
ch := make(chan int)
. - Операции: Отправка (
ch <- value
) и получение (<- ch
) данных являются блокирующими по умолчанию. Это обеспечивает синхронизацию между горутинами. - Типы:
- Небуферизованные: Отправка блокируется, пока другая горутина не будет готова принять данные.
- Буферизованные:
ch := make(chan int, 10)
. Отправка блокируется только когда буфер полон.
Пример:
func worker(jobs <-chan int, results chan<- int) {
for j := range jobs {
// какая-то работа
results <- j * 2
}
}
func main() {
jobs := make(chan int, 5)
results := make(chan int, 5)
// Запуск воркера в отдельной горутине
go worker(jobs, results)
// Отправка данных в канал
for i := 1; i <= 5; i++ {
jobs <- i
}
close(jobs) // Важно закрыть канал, чтобы range в воркере завершился
// Получение результатов
for a := 1; a <= 5; a++ {
fmt.Println(<-results)
}
}
Для более сложных сценариев синхронизации также используются примитивы из пакета sync
, такие как Mutex
, RWMutex
и WaitGroup
.