Расскажите о модели конкурентности в Go. Что такое горутины и каналы?

Ответ

В основе конкурентности в 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.