Ранее уже упоминалось, что аргумент attr функции pthread_create(), имеющий тип pthread_attr_t, может быть использован для задания атрибутов, которые применяются при создании нового потока. Мы не будем углубляться в рассмотрение этих атрибутов (подробности о них ищите в ссылках, перечисленных в конце главы) или изучать прототипы различных функций из состава Pthreads, которые позволяют работать с объектом pthread_attr_t. Мы просто отметим, что данные атрибуты содержат такие сведения, как местоположение и размер стека потока, политику его планирования и приоритет (это похоже на политики планирования в режиме реального времени и приоритеты процессов, описанные в разделах 35.2 и 35.3), а также информацию о том, является ли поток присоединяемым или отсоединенным.
Пример использования этих атрибутов приводится в листинге 29.2, где создается поток, являющийся отсоединенным на момент своего появления (а не в результате последующего вызова pthread_detach()). В самом начале этот код инициализирует структуру с атрибутами с помощью значений по умолчанию, потом устанавливает атрибуты, необходимые для создания отсоединенного потока, и затем создает с помощью этой структуры новый поток. По окончании процедуры создания объект с атрибутами удаляется за ненадобностью.
Листинг 29.2. Создание потока с «отсоединяющим» атрибутом
#include
#include "tlpi_hdr.h"
static void *
threadFunc(void *x)
{
return x;
}
int
main(int argc, char *argv[])
{
pthread_t thr;
pthread_attr_t attr;
int s;
s = pthread_attr_init(&attr); /* Присваиваем значения по умолчанию */
if (s!= 0)
errExitEN(s, "pthread_attr_init");
s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (s!= 0)
errExitEN(s, "pthread_attr_setdetachstate");
s = pthread_create(&thr, &attr, threadFunc, (void *) 1);
if (s!= 0)
errExitEN(s, "pthread_create");
s = pthread_attr_destroy(&attr); /* Больше не нужен */
if (s!= 0)
errExitEN(s, "pthread_attr_destroy");
s = pthread_join(thr, NULL);
if (s!= 0)
errExitEN(s, "pthread_join failed as expected");
exit(EXIT_SUCCESS);
}
В этом разделе мы кратко рассмотрим несколько факторов, которые следует учитывать при выборе между потоками и процессами в качестве основы для своего приложения. Начнем с обсуждения преимуществ многопоточного подхода.
• Обмениваться данными между потоками просто. Такой же обмен данными, но между процессами требует больше затрат (например, создания общего сегмента памяти или использование конвейера).
• Создание потока занимает меньше времени, чем создание процесса; потоки также могут выигрывать в плане скорости переключения контекста.
Однако потоки имеют определенные недостатки по сравнению с процессами.
• Создавая программу на основе потоков, мы должны следить за тем, чтобы используемые функции были потокобезопасными или вызывались совместимым с потоками способом (понятие потоковой безопасности будет описано в разделе 31.1). Многопроцессные приложения могут об этом не заботиться.
• Ошибка в одном потоке (например, изменение памяти через неправильный указатель) может повредить остальные потоки в процессе, поскольку все они используют общее адресное пространство и некоторые другие атрибуты. Для сравнения, процессы являются более изолированными друг от друга.
• Каждый поток соперничает за возможность использования конечного адресного пространства своего процесса. В частности, стек и локальное хранилище каждого потока потребляет часть виртуальной памяти процесса, что делает ее недоступной для других потоков. И хотя адресное пространство имеет довольно большой объем (например, 3 Гбайт для архитектуры x86-32), этот фактор может стать существенным ограничением, сдерживающим процесс от создания большого количества потоков или потоков, требующих много памяти. Отдельные процессы, с другой стороны, могут задействовать весь диапазон свободной виртуальной памяти, ограниченный лишь размерами RAM и пространства подкачки.
Ниже приведены дополнительные моменты, которые могут повлиять на выбор между потоками и процессами.
• Работа с сигналами в многопоточном приложении требует тщательного проектирования (как правило, рекомендуется избегать использования сигналов в многопоточных программах). Больше о потоках и сигналах будет рассказано в разделе 33.2.
• В многопоточном приложении все потоки должны выполнять одну и ту же программу (возможно, задействуя разные ее функции). В многопроцессных приложениях разные процессы способны выполнять разные программы.
• Помимо самих данных, потоки разделяют и другую информацию (например, файловые дескрипторы, действия сигналов, текущий каталог, идентификаторы (имена) пользователя и группы). Это может быть как преимуществом, так и недостатком, в зависимости от приложения.