UNIX: разработка сетевых приложений
26.2. Основные функции для работы с потоками: создание и завершение потоков
В этом разделе мы рассматриваем пять основных функций для работы с потоками, а в следующих двух разделах мы используем эти функции для написания потоковой модификации клиента и сервера TCP.
Функция pthread_create
Когда программа запускается с помощью функции
, создается один поток, называемый начальным (initial) или главным (main). Дополнительные потоки создаются функциейexec.pthread_create#include <pthread.h>int pthread_create(pthread_t* <i>tid</i>, const pthread_attr_t *<i>attr</i>,void *(*<i>func</i>)(void*), void *<i>arg</i>);<i>Возвращает: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</i>Каждый поток процесса обладает собственным идентификатором потока (thread ID), относящимся к типу данных
(как правило, этоpthread_t). При успешном создании нового потока его идентификатор возвращается через указательunsigned int.tidУ каждого потока имеется несколько атрибутов: его приоритет, исходный размер стека, указание на то, должен ли этот поток являться демоном или нет, и т.д. При создании потока мы можем задать эти атрибуты, инициализируя переменную типа
, что позволяет заменить значение, заданное по умолчанию. Обычно мы используем значение по умолчанию, в этом случае мы задаем аргументpthread_attr_tравным пустому указателю.attrНаконец, при создании потока мы должны указать, какую функцию будет выполнять этот поток. Выполнение потока начинается с вызова заданной функции, а завершается либо явно (вызовом
), либо неявно (когда вызванная функция возвращает управление). Адрес функции задается аргументомpthread_exit, и она вызывается с единственным аргументом-указателемfunc. Если этой функции необходимо передать несколько аргументов, следует поместить их в некоторую структуру и передать адрес этой структуры как единственный аргумент функции.argОбратите внимание на объявления
иfunc. Функции передается один аргумент — универсальный указательarg. Это позволяет нам передавать потоку с помощью единственного указателя все, что требуется, и точно так же поток возвращает любые данные, используя этот указатель.void*Возвращаемое значение функций Pthreads — это обычно 0 в случае успешного выполнения или ненулевая величина в случае ошибки. Но в отличие от функций сокетов и большинства системных вызовов, для которых в случае ошибки возвращается -1 и переменной
присваивается некоторое положительное значение (код ошибки), функции Pthreads возвращают сам код ошибки. Например, если функцияerrnoне может создать новый поток, так как мы превысили допустимый системный предел количества потоков, функция возвратит значениеpthread_create. Функции Pthreads не присваивают переменнойEAGAINникаких значений. Соглашение о том, что 0 является индикатором успешного выполнения, а ненулевое значение — индикатором ошибки, не приводит к противоречию, так как все значенияerrno, определенные в заголовочном файлеExxx, являются положительными. Ни одному из имен ошибок Exxx не сопоставлено нулевое значение.<sys/errno.h>Функция pthread_join
Мы можем приостановить выполнение текущего потока и ждать завершения выполнения какого-либо другого потока, используя функцию
. Сравнивая потоки и процессы Unix, можно сказать, что функцияpthread_joinаналогична функцииpthread_create, а функцияfork— функцииpthread_join.waitpid#include <pthread.h>int pthread_join(pthread_t <i>tid</i>, void **<i>status</i>);<i>Возвращает: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</i>Следует указать идентификатор
того потока, завершения которого мы ждем. К сожалению, нет способа указать, что мы ждем завершения любого потока данного процесса (тогда как при работе с процессами мы могли с помощью функцииtidждать завершения любого процесса, задав аргумент идентификатора процесса, равный -1). Мы вернемся к этой проблеме при обсуждении листинга 26.11.waitpidЕсли указатель
непустой, то значение, возвращаемое потоком (указатель на некоторый объект), хранится в ячейке памяти, на которую указываетstatus.statusФункция pthread_self
Каждый поток снабжен идентификатором, уникальным в пределах данного процесса. Идентификатор потока возвращается функцией
и, как мы видели, используется функциейpthread_create. Поток может узнать свой собственный идентификатор с помощью вызоваpthread_join.pthread_self#include <pthread.h>pthread_t pthread_self(void);<i>Возвращает: идентификатор вызывающего потока</i>Сравнивая потоки и процессы Unix, можно отметить, что функция
аналогична функцииpthread_self.getpidФункция pthread_detach
Поток может быть либо присоединяемым (joinable), каким он является по умолчанию, либо отсоединенным (detached). Когда присоединяемый поток завершает свое выполнение, его статус завершения и идентификатор сохраняются, пока другой поток данного процесса не вызовет функцию
. В свою очередь, отсоединенный поток напоминает процесс-демон: когда он завершается, все занимаемые им ресурсы освобождаются и мы не можем отслеживать его завершение. Если один поток должен знать, когда завершится выполнение другого потока, нам следует оставить последний присоединяемым.pthread_joinФункция
изменяет состояние потока, превращая его из присоединяемого в отсоединенный.pthread_detach#include <pthread.h>int pthread_detach(pthread_t <i>tid</i>);