В многопоточных процессах в одной и той же программе одновременно выполняются разные потоки. Все они имеют общие глобальные переменные и кучу, но у каждого из них есть свой отдельный стек для локальных переменных. Разные потоки одного и того же процесса также разделяют целый ряд атрибутов, включая идентификатор процесса, дескриптор открытых файлов, действия сигналов, текущего каталога и ограничения на ресурсы.
Ключевой особенностью потоков является более простой обмен информацией по сравнению с процессами; по этой причине некоторые программные архитектуры лучше ложатся на многопоточный подход, нежели на многопроцессный. Кроме того, в некоторых ситуациях потоки могут демонстрировать лучшую производительность (например, поток создается быстрее, чем процесс), однако этот фактор обычно является вторичным при выборе между потоками и процессами.
Потоки создаются с помощью функции pthread_create(). Любой поток может завершиться независимо от других, используя функцию pthread_exit() (если вызвать exit() в любом из потоков, все они будут немедленно завершены). Если поток не был помечен как отсоединенный (например, с помощью вызова pthread_detach()), он должен быть присоединен другим потоком посредством функции pthread_join(), которая возвращает код завершения присоединенного потока.
[Butenhof, 1996] содержит понятное и в то же время глубокое описание библиотеки Pthreads. Эта библиотека также хорошо освещена в [Robbins & Robbins, 2003]. [Tanenbaum, 2007] предоставляет теоретическое введение в понятие потоков, рассматривая такие темы, как мьютексы, критические регионы, условные переменные, а также обнаружение и обход взаимного блокирования. [Vahalia, 1996] содержит общие сведения о реализации потоков.
29.1. К каким возможным последствиям может привести выполнение потоком следующего кода?
pthread_join(pthread_self(), NULL);
Напишите программу, чтобы увидеть, что же в действительности происходит в системе Linux. Представьте, что у нас есть переменная tid, содержащая идентификатор потока; каким образом поток может избежать выполнения вызова pthread_join(tid, NULL), являющегося эквивалентом вышеприведенной операции?
29.2. Что не так со следующей программой, если не брать во внимание отсутствие проверок на ошибки и объявлений различных переменных и структур?
static void *
threadFunc(void *arg)
{
struct someStruct *pbuf = (struct someStruct *) arg;
/* Выполняем какие-то действия со структурой, на которую указывает 'pbuf' */
}
int
main(int argc, char *argv[])
{
struct someStruct buf;
pthread_create(&thr, NULL, threadFunc, (void *) &buf);
pthread_exit(NULL);
}
30. Потоки выполнения: синхронизация
В этой главе мы опишем два инструмента, с помощью которых потоки могут синхронизировать свои действия: мьютексы и условные переменные. Мьютексы позволяют потокам синхронизировать использование общих ресурсов, чтобы, к примеру, один поток не пытался получить доступ к разделяемой переменной в момент, когда другой поток изменяет ее значение. Условные переменные дополняют это решение, позволяя потокам оповещать друг друга о том, что разделяемая переменная (или другой общий ресурс) изменила свое состояние.
Одно из принципиальных преимуществ потоков заключается в том, что они могут делиться информацией посредством глобальных переменных. Но у этого простого механизма есть и обратная сторона: мы должны следить за тем, чтобы одну и ту же переменную одновременно не пытались изменить сразу несколько потоков или чтобы один поток не пытался прочитать ее содержимое, пока другой поток его обновляет. Термин
В листинге 30.1 показаны проблемы, которые могут возникнуть при неатомарном доступе к разделяемым ресурсам. Эта программа создает два потока, каждый из которых выполняет одну и ту же функцию. Функция содержит цикл, который постепенно инкрементирует глобальную переменную glob. Для этого ее значение копируется в локальную переменную loc, там же инкрементируется и копируется обратно в glob (поскольку переменная loc автоматически попадает в локальный стек потока, каждый поток имеет ее уникальную копию). Количество итераций цикла определяется аргументом командной строки, который передается программе, или значением по умолчанию, если такого аргумента не обнаружено.
Листинг 30.1. Некорректная инкрементация глобальной переменной из двух потоков
threads/thread_incr.c
#include
#include "tlpi_hdr.h"
static int glob = 0;
static void * /* 'arg' раз инкрементируем 'glob' */
threadFunc(void *arg)
{