QNX/UNIX: Анатомия параллелизма - Цилюрик Олег Иванович
Шрифт:
Интервал:
Закладка:
for (int i = 0; i < N; i++) {
pthread_attr_init(pattr);
// ... разнообразные настройки для разных потоков ...
pthread_create(NULL, pattr, &function, NULL);
pthread_attr_destroy(pattr);
}
delete pattr;
Непосредственно манипулировать с полями атрибутной записи, адресуясь к ним по именам полей, крайне опасно. Для этого предусмотрен широкий спектр функций SET/GET:
pthread_attr_getdetachstate()
pthread_attr_setdetachstate()
pthread_attr_getguardsize()
pthread_attr_setguardsize()
pthread_attr_getinheritsched()
pthread_attr_setinheritsched()
pthread_attr_getschedparam()
pthread_attr_setschedparam()
pthread_attr_getschedpolicy()
pthread_attr_setschedpolicy()
pthread_attr_getscope()
pthread_attr_setscope()
pthread_attr_getstackaddr()
pthread_attr_setstackaddr()
pthread_attr_getstacklazy()
pthread_attr_setstacklazy()
pthread_attr_getstacksize()
pthread_attr_setstacksize()
Мы не станем подробно описывать все параметры потока, которые могут быть переопределены атрибутной записью, ведь для этого есть техническая документация QNX, а рассмотрим только наиболее интересные параметры.
Присоединенность
Это одно из самых интересных свойств потока, но одновременно и одно из самых сложных для понимания, поэтому есть смысл остановиться на нем более подробно. Поток может создаваться как ожидаемый ( PTHREAD_CREATE_JOINABLE; таковым он и создается по умолчанию; используется также термин «присоединенный») или отсоединенный ( PTHREAD_CREATE_DETACHED). [18]Например:
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(NULL, &attr, &function, NULL);
Присоединенный поток сохраняет некоторую связь с родителем (мы это рассмотрим, когда речь пойдет о завершения потока), в то время как отсоединенный поток после точки ветвления ведет себя как совершенно автономная сущность: после точки ветвления у родительского потока нет возможности синхронизироваться с его завершением, получить код его завершения или результат выполнения потока.
Можно ожидать завершения присоединенного потока в некотором другом потоке процесса (чаще всего именно в родительском, но это не обязательно) с помощью следующего вызова:
int pthread_join(pthread_t thread, void** value_ptr);
где thread— идентификатор TID ожидаемого потока, который возвращался как первый параметр вызова pthread_create(pthread_t* thread, ...)при его создании или был им же получен после своего создания вызовом pthread_self();
value_ptr— NULLили указатель на область данных (результата выполнения), которую завершающийся поток, возможно, захочет сделать доступной для внешнего мира после своего завершения. Этот указатель функция потока возвращает оператором returnили вызовом pthread_exit().
ПримечаниеВ API QNX присутствует родственная функция (не POSIX) pthread_timedjoin(), отличающаяся тем, что она возвратит ошибку, если синхронизация по завершению не будет достигнута в указанный интервал времени:
int pthread_timedjoin(pthread_t thread, void** value_ptr,
const struct timespec* abstime);
Таким образом, вызов pthread_join(): а) блокирует вызывающий поток, б) является средством синхронизации потоков без использования специальных примитивов синхронизации и в) позволяет потоковой функции завершающегося потока возвратить результат своей работы в точку ожидания его завершения.
ПримечаниеЗначение value_ptr(если оно не было указано как NULL) указывает на возвращенный результат только при нормальном завершении потока. В случае его завершения «извне» (отмены) значение value_ptrустанавливается в PTHREAD_CANCELED(константа).
Если поток предназначен для выполнения автономной работы, не требует синхронизации и не предполагает возвращать значение, он может создаваться как отсоединенный. Поскольку таких случаев достаточно много, даже большинство (например, все множество параллельных сетевых серверов), то такое поведение потока вполне могло бы быть умалчиваемым при создании. Причина несколько ограниченного использования отсоединенных потоков относительно тех случаев, когда это может быть оправданным, состоит, скорее всего, в интуитивной боязни программистов «потерять контроль» над параллельно выполняемой ветвью, хотя зачастую этот контроль бывает чисто иллюзорным (принудительное завершение потока мы подробно рассмотрим позже).
По умолчанию потоки создаются именно как присоединенные, и это аргументируется тем обстоятельством, что такой поток всегда может сделать себя (или другой поток) отсоединенным, вызвав из своей функции потока:
int pthread_detach(pthread_t thread);
Превратить же поток, созданный как отсоединенный, в присоединенный (ожидаемый) нет никакой возможности. Таким образом, это одностороннее преобразование!
Для отсоединенного потока все задействованные им системные ресурсы освобождаются в момент его завершения, а для ожидаемого — в момент выполнения pthread_join()для этого потока из какого-либо другого активного потока.
Пример синхронизации порожденных потоков:
const int THR_NUM = 5; // число дочерних потоков
pthread_t thrarray[THR_NUM];
for (int i = 0; i < THR_NUM, i++)
pthread_create(&thrarray[i], NULL, &thrfunc, NULL);
...