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

Что такое псевдопараллельность?

2.3 Middle🔥 131 комментариев
#Конкурентность и горутины

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

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

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

Что такое псевдопараллельность?

Псевдопараллельность (или кооперативная многозадачность) — это подход к организации одновременного выполнения нескольких задач, при котором они делят один физический поток выполнения, а переключение между ними управляется не системой, а самими задачами или средой выполнения. В отличие от истинной параллельности, где задачи выполняются одновременно на нескольких ядрах CPU, псевдопараллельность создаёт иллюзию параллельного выполнения за счёт быстрого последовательного переключения контекста.

Ключевые принципы псевдопараллельности

  1. Отсутствие истинного параллелизма: задачи выполняются в одном потоке ОС (например, в одном системном потоке).
  2. Кооперативное переключение: каждая задача должна явно "уступить" контроль (например, вызвав yield), чтобы позволить выполняться другим задачам.
  3. Детерминированность: поскольку переключение управляется программой, а не планировщиком ОС, поведение часто более предсказуемо и меньше подвержено race condition.
  4. Эффективность: переключение контекста происходит значительно легче, чем при использовании потоков ОС, так как не требуется взаимодействие с ядром операционной системы.

Псевдопараллельность в Go: горутины

В языке Go псевдопараллельность реализована через горутины (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

  1. Планировщик Go (scheduler): отвечает за распределение горутин по системным потокам (threads). Он использует технику work-stealing для балансировки нагрузки.

  2. Точки вытеснения (preemption points): начиная с Go 1.14, планировщик может вытеснять долго выполняющиеся горутины, но основная модель остаётся кооперативной. Горутина уступает контроль при:

    • Вызове блокирующих операций (каналы, системные вызовы)
    • Операциях ввода-вывода
    • Явном вызове runtime.Gosched()
package main

import (
    "fmt"
    "runtime"
)

func cooperativeTask(id int) {
    for i := 0; i < 5; i++ {
        fmt.Printf("Задача %d: шаг %d\n", id, i)
        // Явно уступаем контроль другим горутинам
        runtime.Gosched()
    }
}

func main() {
    for i := 1; i <= 3; i++ {
        go cooperativeTask(i)
    }
    
    runtime.Gosched()
    time.Sleep(100 * time.Millisecond)
}

Преимущества псевдопараллельности в Go

  1. Низкие накладные расходы: горутины потребляют всего ~2 КБ памяти против 1-2 МБ у обычных потоков.
  2. Быстрое создание и переключение: создание горутины и переключение контекста происходят полностью в пользовательском пространстве.
  3. Упрощённая синхронизация: использование каналов и примитивов синхронизации из пакета sync уменьшает вероятность deadlock и race condition.
  4. Масштабируемость: можно создавать сотни тысяч горутин без значительного падения производительности.

Сравнение с истинной параллельностью

АспектПсевдопараллельность (горутины)Истинная параллельность (потоки ОС)
Память~2 КБ на горутину1-2 МБ на поток (стек по умолчанию)
ПереключениеПользовательское пространство, быстрееПереключение ядра, медленнее
ПараллелизмКооперативный с элементами вытесненияВытесняющий (preemptive)
СинхронизацияКаналы, select, sync-примитивыМьютексы, семафоры, условия
МасштабируемостьДесятки/сотни тысячОбычно сотни потоков

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

Псевдопараллельность идеально подходит для I/O-bound задач, где большую часть времени операции ожидают ввода-вывода:

package main

import (
    "fmt"
    "net/http"
    "time"
)

func fetchURL(url string, ch chan<- string) {
    start := time.Now()
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprintf("Ошибка: %s", url)
        return
    }
    defer resp.Body.Close()
    
    latency := time.Since(start)
    ch <- fmt.Sprintf("%s: %v", url, latency)
}

func main() {
    urls := []string{
        "https://go.dev",
        "https://golang.org",
        "https://google.com",
    }
    
    ch := make(chan string, len(urls))
    
    for _, url := range urls {
        go fetchURL(url, ch) // Каждая горутина выполняется псевдопараллельно
    }
    
    for range urls {
        fmt.Println(<-ch)
    }
}

Заключение

Псевдопараллельность в Go через горутины представляет собой мощную абстракцию, которая сочетает преимущества кооперативной многозадачности (низкие накладные расходы, детерминированность) с возможностью истинного параллельного выполнения на многопроцессорных системах. Это делает Go особенно эффективным для создания высоконагруженных сетевых сервисов и систем обработки данных, где важны как производительность, так и простота управления конкурентностью. Понимание этой модели критически важно для написания корректных и эффективных программ на Go.

Что такое псевдопараллельность? | PrepBro