Потоки против процессов

Почему вкладки одного браузера «живут вместе», а два разных приложения — порознь.

Поток (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 раз

Итог

  • Поток — нить исполнения внутри процесса; у процесса их может быть много.
  • Потоки разделяют код, кучу и файлы, но имеют свой стек и регистры.
  • Потоки дешевле процессов в создании и переключении.
  • Плата за общую память — риск гонок данных и взаимоблокировок.
  • Сбой потока может уронить весь процесс; процессы изолированы лучше.
Проверьте себя
1. Что потоки одного процесса разделяют между собой?
AСтек и регистры
BСчётчик команд
CКучу (heap), код и открытые файлы
DНичего, потоки полностью изолированы
2. Почему создание потока дешевле создания процесса?
AПоток не нужно планировать
BПоток выполняется без процессора
CПоток не имеет собственного стека
DПотоку не создают новое адресное пространство — он работает в уже существующем
3. В чём риск общей памяти у потоков?
AВозникают гонки данных и взаимоблокировки
BПамять быстро заканчивается
CПотоки не видят данные друг друга
DПамять становится доступна другим процессам