Ответ
В Go основной моделью конкурентности являются горутины (goroutines), а не прямая работа с потоками ОС. Горутины — это легковесные потоки, управляемые средой выполнения Go.
Плюсы (преимущества горутин):
- Легковесность: Создание горутины гораздо дешевле, чем создание потока ОС. Горутина изначально занимает всего несколько килобайт в стеке, который может расти по мере необходимости. Можно легко запустить сотни тысяч горутин.
- Простота запуска: Горутина запускается простым добавлением ключевого слова
go
перед вызовом функции:go myFunction()
. - Эффективное использование CPU: Среда выполнения Go (Go runtime) мультиплексирует множество горутин на небольшое количество потоков ОС, что позволяет эффективно утилизировать все ядра процессора.
- Безопасная коммуникация через каналы: Вместо традиционного подхода с общей памятью и мьютексами, Go поощряет передачу данных между горутинами через каналы (channels). Это снижает риск возникновения состояний гонки (race conditions).
Минусы и сложности:
- Риск утечки горутин: Если горутина блокируется в ожидании данных из канала, в который никто никогда не напишет, она останется в памяти навсегда. Необходимо аккуратно управлять их жизненным циклом.
- Сложность отладки: Отладка конкурентного кода может быть сложной из-за непредсказуемого порядка выполнения.
- Состояния гонки все еще возможны: Если разработчик решает использовать общую память вместо каналов без должной синхронизации (например, без мьютексов), состояния гонки все равно могут возникнуть.
Пример: Пул воркеров на горутинах и каналах
package main
import (
"fmt"
"time"
)
// worker — это горутина, которая получает задания из канала `jobs`
// и отправляет результат в канал `results`.
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Воркер %d начал задание %dn", id, j)
time.Sleep(time.Second) // Имитация работы
results <- j * 2
fmt.Printf("Воркер %d закончил задание %dn", id, j)
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// Запускаем 3 воркера в виде горутин
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Отправляем 5 заданий в канал `jobs`
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs) // Закрываем канал, чтобы воркеры завершились после выполнения всех заданий
// Собираем результаты
for a := 1; a <= numJobs; a++ {
<-results
}
fmt.Println("Все задания выполнены.")
}