Ответ
Основное различие заключается в изоляции памяти.
Процесс — это программа в состоянии выполнения. Операционная система выделяет каждому процессу изолированное виртуальное адресное пространство. Процессы не могут напрямую обращаться к памяти друг друга. Для обмена данными они используют механизмы межпроцессного взаимодействия (IPC), которые работают медленнее, чем прямой доступ к памяти.
Поток (Thread) — это единица выполнения внутри процесса. Все потоки одного процесса работают в общем адресном пространстве. Это означает, что они разделяют общие данные (глобальные переменные, кучу), что позволяет им обмениваться информацией очень быстро. Однако это также создает риск состояния гонки (race condition), когда несколько потоков одновременно обращаются к общим данным. Для безопасной работы требуется синхронизация (мьютексы, атомарные операции).
Горутины в Go
Горутины — это легковесные потоки, управляемые средой выполнения Go, а не напрямую операционной системой. Они работают в том же адресном пространстве, что и основной процесс Go, и сталкиваются с теми же проблемами синхронизации, что и обычные потоки.
Пример проблемы (гонка данных):
package main
import (
"fmt"
"sync"
)
func main() {
var counter int
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
counter++ // Опасно: одновременная запись
}
}()
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
counter++ // Опасно: одновременная запись
}
}()
wg.Wait() // Ждем завершения обеих горутин
fmt.Println("Результат без синхронизации:", counter) // Результат непредсказуем (не всегда 2000)
}
Решение с использованием мьютекса:
package main
import (
"fmt"
"sync"
)
func main() {
var counter int
var wg sync.WaitGroup
var mu sync.Mutex // Мьютекс для защиты counter
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
mu.Lock()
counter++
mu.Unlock()
}
}()
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
mu.Lock()
counter++
mu.Unlock()
}
}()
wg.Wait()
fmt.Println("Результат с мьютексом:", counter) // Всегда 2000
}