В чём разница между горутиной и тредом?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между горутиной и тредом
Горутины (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 тредами.