← Назад к вопросам

Какие знаешь виды многозадачности?

2.2 Middle🔥 161 комментариев
#Конкурентность и горутины#Операционные системы и Linux

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Виды многозадачности в Go

В Go многозадачность реализована через легковесные потоки (горутины) и каналы, что принципиально отличается от классических подходов. Вот основные виды и механизмы многозадачности, которые важно знать Go-разработчику.

1. Параллелизм (Concurrency) vs Параллелизм (Parallelism)

Это фундаментальное различие:

  • Concurrency — это структура программы, способной выполнять несколько задач возможно, в разное время. Это про дизайн: программы организуются как набор независимо выполняющихся процессов.
  • Parallelism — это фактическое одновременное выполнение задач на нескольких ядрах CPU. Это про исполнение.

Go отлично поддерживает оба подхода, но concurrency — это его философия.

2. Горутины (Goroutines)

Горутины — это легковесные потоки, управляемые рантаймом Go, а не ОС.

package main

import (
    "fmt"
    "time"
)

func printNumbers(prefix string) {
    for i := 1; i <= 3; i++ {
        fmt.Printf("%s: %d\n", prefix, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // Запуск горутин (многозадачность)
    go printNumbers("Горутина 1")
    go printNumbers("Горутина 2")
    
    // Даём время на выполнение горутин
    time.Sleep(1 * time.Second)
}

Ключевые особенности:

  • Запуск через go func()
  • Потребление памяти ~2KB против 1-2MB у обычных потоков
  • Быстрое создание и переключение
  • Мультиплексируются на небольшом количестве OS-потоков

3. Каналы (Channels)

Каналы — это типизированные конвейеры для связи между горутинами, реализующие модель CSP (Communicating Sequential Processes).

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- job * 2 // Отправляем результат
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    
    // Запускаем воркеров
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // Отправляем задания
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)
}

4. Мьютексы и синхронизация

Для защиты общих данных используется пакет sync:

  • sync.Mutex — эксклюзивная блокировка
  • sync.RWMutex — разделение на чтение/запись
  • sync.WaitGroup — ожидание завершения горутин
  • sync.Once — однократное выполнение
type SafeCounter struct {
    mu    sync.RWMutex
    value int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *SafeCounter) Value() int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.value
}

5. Select и мультиплексирование

Конструкция select позволяет ожидать нескольких операций с каналами:

select {
case msg1 := <-ch1:
    fmt.Println("Получено из ch1:", msg1)
case msg2 := <-ch2:
    fmt.Println("Получено из ch2:", msg2)
case <-time.After(1 * time.Second):
    fmt.Println("Таймаут")
default:
    fmt.Println("Нет готовых каналов")
}

6. Context для отмены и таймаутов

Пакет context стандартизирует отмену операций, таймауты и передачу значений:

func process(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("Операция отменена:", ctx.Err())
        return
    case <-time.After(2 * time.Second):
        fmt.Println("Обработка завершена")
    }
}

7. Worker Pools (Пул воркеров)

Паттерн для ограничения параллелизма и эффективного использования ресурсов:

func workerPool(tasks []Task, numWorkers int) []Result {
    tasksCh := make(chan Task, len(tasks))
    resultsCh := make(chan Result, len(tasks))
    
    // Запускаем воркеров
    for i := 0; i < numWorkers; i++ {
        go worker(tasksCh, resultsCh)
    }
    
    // Отправляем задачи
    for _, task := range tasks {
        tasksCh <- task
    }
    close(tasksCh)
    
    // Собираем результаты
    var results []Result
    for range tasks {
        results = append(results, <-resultsCh)
    }
    return results
}

Сравнительная таблица подходов

ПодходПреимуществаНедостаткиЛучший сценарий
ГорутиныЛегковесные, тысячи одновременноТребуют управленияАсинхронные задачи
КаналыБезопасная коммуникацияМогут создавать deadlockPipeline, Pub/Sub
МьютексыПрямой доступ к памятиРиск deadlock, сложнееПростые структуры данных
SelectМультиплексированиеТолько для каналовОжидание нескольких событий
Worker PoolКонтроль ресурсовСложнее настройкаОбработка очередей

Практические рекомендации

  1. Предпочитайте каналы мьютексам там, где это уместно — они лучше выражают намерения
  2. Используйте буферизованные каналы осторожно — они могут скрывать проблемы дизайна
  3. Всегда закрывайте каналы отправителем, чтобы избежать паник
  4. Контролируйте утечки горутин через context или сигнальные каналы
  5. Профилируйте параллельные программы с помощью pprof и race detector

Многозадачность в Go — это не просто техническая возможность, а философия дизайна программ. Элегантная работа с горутинами и каналами позволяет создавать эффективные, масштабируемые и поддерживаемые concurrent-системы, что является одним из ключевых преимуществ языка перед традиционными подходами с использованием потоков ОС. Умение правильно выбирать между различными видами многозадачности — важный навык senior Go-разработчика.