Потоки и их отличие от процессов
Что такое поток
Поток (thread) — независимая последовательность выполнения внутри процесса. У процесса может быть много потоков; все они работают в одном адресном пространстве.
Процесс — единица владения ресурсами. Поток — единица планирования: планировщик распределяет процессорное время между потоками.
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
Что у потоков одного процесса общее, а что у каждого своё?
| Общее для всех потоков | Своё у каждого потока | |
|---|---|---|
| Адресное пространство (код, глобальные данные, куча) | ||
| Стек | ||
| Регистры и счётчик команд | ||
| Таблица файловых дескрипторов | ||
| Обработчики сигналов (диспозиции) | ||
| Маска блокированных сигналов | ||
| PID (getpid()) | ||
| TID (gettid()) | ||
| errno |
Создайте поток, выполняющий функцию worker с аргументом NULL, и дождитесь его завершения.
Идентификатор потока — переменная pthread_t t.
pthread_t t;
pthread_create(&t, NULL, worker, NULL);
pthread_join(t, NULL);
Чем создание потока отличается от создания процесса через fork?
Какие флаги clone нужны, чтобы получить поток, а какие — процесс как при fork?
| Поток (pthread_create) | Процесс (fork) | |
|---|---|---|
| CLONE_VM — общее адресное пространство | ||
| CLONE_FILES — общая таблица дескрипторов | ||
| CLONE_SIGHAND — общие обработчики сигналов | ||
| CLONE_THREAD — та же группа потоков (тот же PID) | ||
| SIGCHLD — сигнал родителю при завершении |
Глобальная переменная int x = 0;. Процесс делает fork(), и потомок выполняет x = 5;.
Чему равно x в родителе после завершения потомка?
Войдите, чтобы отмечать пройденные уроки.