Потоки против процессов
Почему вкладки одного браузера «живут вместе», а два разных приложения — порознь.
Поток (thread) — это отдельная нить исполнения внутри процесса. У процесса может быть много потоков, и все они разделяют общую память процесса.
Главная идея
Процесс — это «дом» с собственной территорией (адресным пространством). Поток — это «жилец» внутри дома. В одном доме может жить несколько жильцов, и они пользуются общей кухней и гостиной (общей памятью). А вот разные дома (процессы) изолированы заборами.
Зачем нужны потоки? Чтобы делать несколько вещей внутри одной программы параллельно: один поток рисует интерфейс, другой загружает файл, третий считает. И при этом им легко обмениваться данными — память-то общая.
Что общее, а что своё
| Ресурс | Общий для потоков? |
| Код (text) | да, общий |
| Куча (heap), глобальные данные | да, общие |
| Открытые файлы | да, общие |
| Стек | нет, у каждого потока свой |
| Регистры, счётчик команд | нет, у каждого свои |
У каждого потока свой стек и свои регистры — иначе они не могли бы выполнять разный код одновременно. Но куча общая, поэтому потоки видят одни и те же объекты. Отсюда и удобство, и опасность: общая память — источник гонок данных (это тема четвёртого раздела).
Потоки против процессов: сравнение
| Критерий | Процессы | Потоки |
| Память | изолированная | общая внутри процесса |
| Создание | дороже | дешевле |
| Переключение | дороже | дешевле |
| Обмен данными | через IPC | напрямую через память |
| Надёжность | сбой одного не валит других | сбой потока может уронить весь процесс |
Плюсы и минусы многопоточности
Плюсы: отзывчивость (UI не зависает, пока идёт долгая задача), использование нескольких ядер, лёгкий обмен данными, экономия памяти по сравнению с процессами.
Минусы: сложность — общая память порождает гонки и взаимоблокировки; один упавший поток может обрушить весь процесс; отладка многопоточного кода трудна.
Модели потоков
Потоки бывают пользовательскими (управляет библиотека) и ядерными (управляет ядро). Современные ОС обычно отображают потоки приложения на потоки ядра, что позволяет реально использовать несколько ядер процессора.
Реальный код с потоками
Так выглядит создание потоков на C (POSIX threads). Это системный код — он не запускается в браузере, только подсветка.
#include <pthread.h>
void* worker(void* arg) {
// работа потока
return NULL;
}
pthread_t t1, t2;
pthread_create(&t1, NULL, worker, NULL); // создать поток
pthread_create(&t2, NULL, worker, NULL);
pthread_join(t1, NULL); // дождаться завершения
pthread_join(t2, NULL);
Сравним стоимость: процессы или потоки
Смоделируем условную стоимость создания и переключения. Цифры иллюстративные, но порядок верный: потоки заметно дешевле процессов.
process_create = 1000 # условные такты
thread_create = 100
process_switch = 300
thread_switch = 50
n = 8 # сколько единиц работы запустить
print(f"{n} процессов: создание {n*process_create}, переключение {n*process_switch}")
print(f"{n} потоков: создание {n*thread_create}, переключение {n*thread_switch}")
ratio = (n*process_create) / (n*thread_create)
print(f"Создание потоков дешевле в {ratio:.0f} раз")
Вывод:
8 процессов: создание 8000, переключение 2400 8 потоков: создание 800, переключение 400 Создание потоков дешевле в 10 раз
Итог
- Поток — нить исполнения внутри процесса; у процесса их может быть много.
- Потоки разделяют код, кучу и файлы, но имеют свой стек и регистры.
- Потоки дешевле процессов в создании и переключении.
- Плата за общую память — риск гонок данных и взаимоблокировок.
- Сбой потока может уронить весь процесс; процессы изолированы лучше.