Как в Go реализуется конкурентное выполнение задач и какую роль в этом играет планировщик?

Ответ

В Go этот подход основан на горутинах (goroutines) и каналах (channels), что соответствует философии "Do not communicate by sharing memory; instead, share memory by communicating" (Не общайтесь, разделяя память; вместо этого разделяйте память, общаясь).


  • Горутины (Goroutines) — это легковесные потоки, управляемые рантаймом Go, а не напрямую операционной системой. Их создание и переключение между ними гораздо дешевле, чем у системных потоков.



  • Каналы (Channels) — это типизированные "конвейеры", через которые горутины могут безопасно обмениваться данными, избегая состояния гонки.



  • Планировщик Go (Go Scheduler) — это ключевой компонент рантайма, который эффективно распределяет M горутин по N потокам операционной системы (модель M:N). Он самостоятельно решает, какая горутина будет выполняться в какой момент, обеспечивая высокую производительность и утилизацию CPU.


Пример (Пул воркеров):

// worker — горутина, которая читает задачи из канала `tasks` и отправляет результат в `results`.
func worker(id int, tasks <-chan int, results chan<- int) {
    for task := range tasks {
        fmt.Printf("Воркер %d обработал задачу %dn", id, task)
        time.Sleep(time.Second) // Имитация работы
        results <- task * 2
    }
}

func main() {
    taskCount := 10
    workerCount := 3

    tasks := make(chan int, taskCount)
    results := make(chan int, taskCount)

    // Запуск пула воркеров (горутин)
    for i := 1; i <= workerCount; i++ {
        go worker(i, tasks, results)
    }

    // Отправка задач в канал
    for i := 1; i <= taskCount; i++ {
        tasks <- i
    }
    close(tasks) // Важно закрыть канал, чтобы воркеры завершили работу

    // Сбор результатов
    for i := 1; i <= taskCount; i++ {
        <-results
    }
    fmt.Println("Все задачи выполнены")
}