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

В чём разница между горутиной и тредом?

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

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

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

Разница между горутиной и тредом

Горутины (goroutines) и треды (threads) — это две разные модели параллелизма. Хотя горутины выглядят как потоки, они имеют фундаментальные отличия, делающие их значительно более эффективными.

Определения

Горутина Горутина — это легковесная абстракция, управляемая Go runtime. Множество горутин может работать на одном OS треде, переключаясь между ними асинхронно.

Тред (OS Thread) Тред — это единица параллелизма, управляемая операционной системой. Каждый тред имеет собственный стек и контекст, требует значительных ресурсов.

Основные различия

1. Использование памяти

import "runtime"
import "time"

func main() {
    // Создаём 1 миллион горутин
    for i := 0; i < 1000000; i++ {
        go func() {
            time.Sleep(10 * time.Second)
        }()
    }
    
    // Это работает! Программа использует ~200MB памяти
    time.Sleep(11 * time.Second)
    fmt.Println("Готово")
}

В контрасте:

  • Горутина: ~2-4 килобайта памяти
  • OS Тред: 1-8 мегабайт памяти

Это означает, что горутин можно создать в 1000 раз больше, чем тредов!

2. Планирование и переключение контекста Горутины управляются Go runtime, треды — операционной системой.

┌─────────────────────────────────────────┐
│      Go Runtime Scheduler               │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐ │
│  │ Gorout1 │  │ Gorout2 │  │ Gorout3 │ │
│  └────┬────┘  └────┬────┘  └────┬────┘ │
│       │             │             │     │
│  ┌────────────────────────────────────┐ │
│  │   Распределены на OS треды         │ │
│  └────────────────────────────────────┘ │
└─────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────┐
│     Операционная система (ОС)           │
│   ┌──────────┐ ┌──────────┐           │
│   │ OS Thread│ │ OS Thread│  ← типично 4-8 тредов
│   └──────────┘ └──────────┘           │
└─────────────────────────────────────────┘

3. Создание и уничтожение

// Горутина - дешёвая операция
go func() {
    fmt.Println("Горутина")
}()

// OS тред - дорогая операция
import "sync"

var mu sync.Mutex
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
    defer wg.Done()
    // Это всё ещё горутина, но демонстрирует, что её дешево создавать
}()
wg.Wait()

4. Блокирование

import "time"
import "sync"

// ❌ Горутина, блокирующая на IO (это OK!)
go func() {
    time.Sleep(10 * time.Second)  // Не блокирует другие горутины
    fmt.Println("Готово")
}()

// Тред, блокирующий на IO (это проблема!)
// Если тред блокируется, то вычисления на нём тоже блокируются

Переключение контекста

Go Runtime (горутины)

func main() {
    for i := 0; i < 10; i++ {
        go func(id int) {
            for j := 0; j < 5; j++ {
                fmt.Printf("Goroutine %d: %d\n", id, j)
                runtime.Gosched()  // Явно даём шанс другим горутинам
            }
        }(i)
    }
    
    time.Sleep(1 * time.Second)
}

Go runtime может переключаться между горутинами:

  • При вызове функций из пакета net, os, io
  • При попытке отправки/получения на каналах
  • При вызове runtime.Gosched()
  • Примерно каждые 10 миллисекунд

OS (треды)

  • Операционная система переключается между тредами
  • На многоядерной системе треды могут работать параллельно
  • Переключение занимает больше времени

Синхронизация

Горутины - каналы (рекомендуется)

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Println("Worker", id, "processing job", j)
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    
    // 3 горутины-воркера
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)
    
    for a := 1; a <= 9; a++ {
        <-results
    }
}

Треды - мьютексы и синхронизация

import "sync"

var counter = 0
var mu sync.Mutex

func increment() {
    mu.Lock()
    counter++
    mu.Unlock()
}

Когда использовать горутины vs треды

Горутины (Go) ✅ Легко создавать миллионы ✅ Минимальное потребление памяти ✅ Быстрое переключение контекста ✅ Простая коммуникация через каналы

OS Треды (C, Java, etc.) ✅ Реальный параллелизм на многоядерных системах ✅ Выполнение CPU-bound задач ✅ При необходимости использования блокирующих библиотек ✅ Лучше для некоторых сценариев real-time

Практический пример: веб-сервер

import "net/http"

func handleRequest(w http.ResponseWriter, r *http.Request) {
    // Каждый запрос - в отдельной горутине
    // Может обработать миллионы одновременных запросов!
    w.Write([]byte("Hello"))
}

func main() {
    http.HandleFunc("/", handleRequest)
    http.ListenAndServe(":8080", nil)
}

На Java потребовалось бы либо использовать сложные асинхронные фреймворки, либо иметь тредпул с ограниченным количеством тредов.

Таблица сравнения

ПараметрГорутинаOS Тред
Память~2-4 КБ1-8 МБ
Количество1,000,000+1,000
СозданиеОчень быстроМедленно
ПереключениеБыстро (~µs)Медленнее (~ms)
БлокированиеНе блокирует другиеБлокирует
СинхронизацияКаналыМьютексы

Горутины — это одна из ключевых особенностей Go, позволяющая писать эффективный конкурентный код без сложности работы с OS тредами.

В чём разница между горутиной и тредом? | PrepBro