Потоки и их отличие от процессов

⏱ 30 мин · 🧩 заданий: 5

Что такое поток

Поток (thread) — независимая последовательность выполнения внутри процесса. У процесса может быть много потоков; все они работают в одном адресном пространстве.

процесс (PID 100) ┌──────────────────────────────────────────────────────────┐ │ общее: код, глобальные данные, куча, файлы, обработчики │ │ сигналов, текущий каталог, PID, UID │ │ │ │ поток 1 (TID 100) поток 2 (TID 101) поток 3 (TID 102)│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ регистры │ │ регистры │ │ регистры │ │ │ │ стек │ │ стек │ │ стек │ │ │ │ маска сигн. │ │ маска сигн. │ │ маска сигн. │ │ │ │ errno, TLS │ │ errno, TLS │ │ errno, TLS │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └──────────────────────────────────────────────────────────┘

Процесс — единица владения ресурсами. Поток — единица планирования: планировщик распределяет процессорное время между потоками.

POSIX Threads

#include <pthread.h>
#include <stdio.h>

int counter = 0;                     /* общая для всех потоков */

void *worker(void *arg)
{
    int id = *(int *)arg;
    printf("поток %d\n", id);
    return NULL;
}

int main(void)
{
    pthread_t t[4];
    int ids[4];
    for (int i = 0; i < 4; i++) {
        ids[i] = i;
        pthread_create(&t[i], NULL, worker, &ids[i]);
    }
    for (int i = 0; i < 4; i++)
        pthread_join(t[i], NULL);    /* аналог wait для потоков */
    return 0;
}
gcc prog.c -o prog -pthread

Завершение: возврат из функции потока или pthread_exit. Если main вернётся (то есть вызовет exit), завершатся все потоки процесса.

Потоки в Linux: clone

В ядре Linux нет отдельной сущности «поток»: и процессы, и потоки — это task_struct, создаваемые системным вызовом clone. Разница — в флагах, говорящих, что разделить с родителем, а что скопировать:

Флаг Если указан — общее
CLONE_VM адресное пространство (таблицы страниц не копируются)
CLONE_FILES таблица файловых дескрипторов
CLONE_FS корень, текущий каталог, umask
CLONE_SIGHAND обработчики сигналов
CLONE_THREAD та же группа потоков: тот же PID (TGID), сигналы процессу — общие
/* примерно так вызывает clone библиотека pthread */
clone(start, stack, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
                    CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS | ..., arg);

/* а так — fork */
clone(..., SIGCHLD, ...);   /* ничего не разделять, при завершении послать SIGCHLD */

getpid() возвращает TGID — общий для всех потоков, gettid() — идентификатор конкретного потока. В /proc/<pid>/task/ — по каталогу на поток.

Создание потока vs создание процесса

Это вопрос 3(б) экзамена:

fork (процесс) pthread_create (поток)
Дескриптор task_struct новый новый
PID новый тот же (новый только TID)
Адресное пространство копируются VMA и таблицы страниц, страницы — copy-on-write общее, ничего не копируется
Стек копия стека родителя (COW) новый стек выделяется (обычно mmap, 8 МиБ)
Файловые дескрипторы копия таблицы (общие открытые файлы) одна общая таблица
Обработчики сигналов копия общие
Маска сигналов копия своя у потока (копируется из создающего)
Возврат дважды: 0 и PID потомка новый поток начинает с функции start
Стоимость дороже дешевле
Переключение смена cr3, сброс TLB между потоками одного процесса cr3 не меняется
Изоляция ошибка потомка не портит память родителя общая память: ошибка одного потока может испортить данные всех, SIGSEGV убивает весь процесс

Формулировка для экзамена

В Linux и процесс, и поток создаются вызовом clone и описываются task_struct. При создании процесса ядро копирует контексты родителя — таблицу дескрипторов, обработчики сигналов, а адресное пространство дублирует с copy-on-write и получает новый PID. При создании потока те же структуры разделяются (флаги CLONE_VM, CLONE_FILES, CLONE_SIGHAND, CLONE_THREAD): копируется только регистровый контекст, выделяются новый стек и стек ядра, PID остаётся прежним. Поэтому поток создаётся быстрее, но потоки не изолированы друг от друга.

Потоки и сигналы

  • Обработчики сигналов общие для процесса.
  • Маска блокировки — своя у каждого потока (pthread_sigmask).
  • Сигнал, посланный процессу (kill), доставляется любому потоку, который его не блокирует.
  • Сигнал от аппаратного исключения (SIGSEGV, SIGFPE) доставляется тому потоку, который его вызвал.
  • Действие «завершить» или «остановить» применяется ко всему процессу.

Итог

  • Поток — единица планирования внутри процесса; потоки делят адресное пространство, дескрипторы, обработчики сигналов.
  • Своё у потока: стек, регистры, маска сигналов, TID, errno.
  • В Linux и процессы, и потоки создаются clone; разница — флаги разделения.
  • Создание потока дешевле: не копируются таблицы страниц и таблица файлов, не меняется PID.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Заполнить таблицу

Что у потоков одного процесса общее, а что у каждого своё?

Общее для всех потоковСвоё у каждого потока
Адресное пространство (код, глобальные данные, куча)
Стек
Регистры и счётчик команд
Таблица файловых дескрипторов
Обработчики сигналов (диспозиции)
Маска блокированных сигналов
PID (getpid())
TID (gettid())
errno
Отметьте ячейку — «+», пустая — «−».
Задание 2 Написать код

Создайте поток, выполняющий функцию worker с аргументом NULL, и дождитесь его завершения. Идентификатор потока — переменная pthread_t t.

Задание 3 Несколько вариантов

Чем создание потока отличается от создания процесса через fork?

Задание 4 Заполнить таблицу

Какие флаги clone нужны, чтобы получить поток, а какие — процесс как при fork?

Поток (pthread_create)Процесс (fork)
CLONE_VM — общее адресное пространство
CLONE_FILES — общая таблица дескрипторов
CLONE_SIGHAND — общие обработчики сигналов
CLONE_THREAD — та же группа потоков (тот же PID)
SIGCHLD — сигнал родителю при завершении
Отметьте ячейку — «+», пустая — «−».
Задание 5 Ввод ответа

Глобальная переменная int x = 0;. Процесс делает fork(), и потомок выполняет x = 5;. Чему равно x в родителе после завершения потомка?

Войдите, чтобы отмечать пройденные уроки.