Ответ
Да, имею большой опыт. Модель конкурентности в Go — одна из ключевых особенностей языка. Она построена на горутинах (goroutines) и каналах (channels), что соответствует философии: "Не обменивайтесь данными, разделяя память; вместо этого, разделяйте память, обмениваясь данными".
Ключевые инструменты:
- Горутины: Это легковесные потоки, управляемые средой выполнения Go (Go runtime). Их создание очень дешево по сравнению с системными потоками, что позволяет запускать сотни тысяч горутин одновременно.
- Каналы: Это типизированные "трубы", через которые горутины могут безопасно обмениваться данными, избегая состояния гонки.
select
: Оператор, который позволяет горутине ждать нескольких операций с каналами одновременно, подобноswitch
, но для каналов.
Пример: Worker Pool
Этот паттерн отлично демонстрирует совместную работу горутин и каналов для параллельной обработки задач.
package main
import (
"fmt"
"time"
)
// worker получает задания из канала `jobs` и отправляет результат в `results`.
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("worker %d started job %dn", id, j)
time.Sleep(time.Second) // Имитация работы
fmt.Printf("worker %d finished job %dn", id, j)
results <- j * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// Запускаем 3 воркера. Они будут заблокированы, пока в канал `jobs` не поступят данные.
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Отправляем 5 заданий в канал `jobs` и затем закрываем его.
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// Собираем результаты. Цикл завершится, когда все 5 результатов будут получены.
for a := 1; a <= numJobs; a++ {
<-results
}
fmt.Println("All jobs are done.")
}
Помимо каналов, я также активно использовал примитивы синхронизации из пакета sync
для ситуаций, где необходимо разделение памяти.