Ответ
В Go параллельность реализована через горутины (goroutines) — легковесные потоки, управляемые рантаймом языка. Они работают поверх системных потоков, но потребляют значительно меньше ресурсов (стартовый стек ~2 КБ).
Каналы (channels) — основной способ коммуникации между горутинами. Они обеспечивают безопасную синхронизацию и передачу данных, следуя принципу "Не обменивайтесь данными путем совместного использования памяти; вместо этого обменивайтесь памятью путем коммуникации" (Don't communicate by sharing memory; share memory by communicating).
Пример использования горутин и каналов:
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Рабочий %d начал задачу %dn", id, j)
time.Sleep(time.Millisecond * 500) // Имитация работы
fmt.Printf("Рабочий %d закончил задачу %dn", id, j)
results <- j * 2
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
// Запуск 3 горутин-рабочих
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Отправка 5 задач в канал jobs
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs) // Важно закрыть канал, чтобы рабочие знали, когда задач больше нет
// Получение результатов от рабочих
for r := 1; r <= 5; r++ {
fmt.Println("Получен результат:", <-results)
}
}
Ключевые особенности и плюсы:
- Простота создания: Горутины создаются очень легко с помощью ключевого слова
go
перед вызовом функции (go func()
). - Эффективность: Легковесность горутин позволяет запускать десятки и сотни тысяч одновременно.
- Встроенные примитивы синхронизации: Помимо каналов, Go предоставляет пакет
sync
с такими примитивами, какsync.Mutex
(мьютексы),sync.RWMutex
(мьютексы для чтения/записи),sync.WaitGroup
(для ожидания завершения группы горутин) иsync.Once
. - Планировщик Go: Рантайм Go включает собственный планировщик (scheduler), который эффективно распределяет горутины по доступным логическим ядрам CPU (потокам ОС), управляя их переключением и мультиплексированием.
Нюансы и потенциальные проблемы:
- Утечки горутин (Goroutine Leaks): Если горутина запущена, но никогда не завершается (например, из-за блокировки на канале, который никогда не будет закрыт или прочитан), она будет продолжать потреблять ресурсы. Это может привести к исчерпанию памяти.
- Состояние гонки (Race Conditions): Хотя каналы являются предпочтительным способом обмена данными, при прямом доступе к общим данным из нескольких горутин без должной синхронизации (мьютексов) могут возникать состояния гонки.
- Взаимные блокировки (Deadlocks): Неправильное использование каналов или мьютексов может привести к ситуации, когда две или более горутины бесконечно ждут друг друга, блокируя выполнение программы.