MyWackoSite: КурсОперационныеСистемы/ПрактикумPosixThreads/PthreadLectures/CreateExit ...

Home Page | Каталог | Изменения | НовыеКомментарии | Пользователи | Регистрация | Вход:  Пароль:  
Это старая версия КурсОперационныеСистемы/ПрактикумPosixThreads/PthreadLectures/CreateExit за 2007-04-09 11:47:52..
В ходе этого раздела вы изучите

Создание нитей с атрибутами по умолчанию


В POSIX Thread API нить создается библиотечной функцией pthread_create(3C).

Параметры этой функции:

pthread_t * thread – Выходной параметр. Указатель на переменную, в которой при успешном завершении будет размещен идентификатор нити.
const pthread_attr_t * attr – Входной параметр. Указатель на структуру, в которой заданы атрибуты нити (рассматривается на следующей лекции). Если этот указатель равен NULL, используются атрибуты по умолчанию.
void *(*start_routine)(void*) – Входной параметр. Указатель на функцию, которая будет запущена во вновь созданной нити.
void * arg – Входной параметр. Значение, которое будет передано в качестве параметра start_routine.

Возвращаемое значение
0 при успешном завершении
Код ошибки при неудачном завершении
Большинство других функций POSIX Threads API используют аналогичное соглашение о кодах возврата. Если в нашем учебном пособии у функции не указано описание кода возврата, значит, что она возвращает 0 при успешном завершении и код ошибки при ошибке. В страницах man(1) всегда указывается точное описание кода возврата всех функций.

Коды ошибок
Значения кодов ошибок определены в виде символов препроцессора в файле errno.h
EAGAIN – системе не хватает ресурсов для создания нити. Возможно, не хватает памяти под стек, исчерпан архитектурный лимит на количество нитей в процессе (PTHREAD_THREADS_MAX) либо административное ограничение на количество нитей. Как и у остальных системных вызовов, код EAGAIN означает, что повторный вызов функции с теми же параметрами может не привести к ошибке.
EINVAL – один из параметров имеет недопустимое значение. Например, указатель на start_routine указывает на страницу памяти, исполнение которой запрещено.
EPERM – вы не имеете полномочий для исполнения нити с заданными атрибутами. Например, вы не можете установить заданные в структуре attr класс планирования или приоритет.

Пример 1


Важное замечание

Параметр функции нити описан как void *, но библиотека никогда не пытается обращаться к нему как к указателю. Поэтому этот указатель можно использовать либо как ссылку на структуру (блок параметров нити), либо для передачи скалярного значения.

При передаче структур данных в качестве параметра нужно проявлять осторожность.

Во первых, не следует передавать структуры данных, размещенные в стеке родительской нити, то есть переменные с классом памяти auto и блоки памяти, размещенные при помощи alloca(3C). Действительно, если ваша нить вернет управление из текущей функции или вообще завершится до того, как созданная нить начнет работать с блоком параметров, получится, что вы передали в качестве параметра висячую ссылку.

Во вторых, при передаче структур данных, размещенных при помощи malloc(3C) или оператора new языка C++, необходимо решить вопрос о том, кто будет освобождать эту структуру. Если структура не будет освобождена при помощи free(3C) или оператора delete, это приведет к утечке памяти. Существует несколько решений этого вопроса, приемлемых в разных ситуациях.

Одно из решений состоит в том, что родитель размещает структуру, а созданная нить ее освобождает. Но обычно это считается дурным тоном при программировании на C/C++ (хорошим тоном считается, чтобы вызываемая функция не знала, каким образом выделена память под переданные ей параметры).

Другое решение состоит в том, что родитель размещает структуру, дожидается завершения потомка при помощи вызова pthread_join(3C) и освобождает структуру. В рамках этого подхода можно передавать как структуры данных, созданные при помощи malloc(3C), так и структуры данных, размещенные в стеке родителя. Проблема этого подхода состоит в том, что если родитель будет принудительно завершен при помощи pthread_cancel(3С), он может не дождаться завершения своих потомков, а это приведет либо к утечке памяти, либо к висячим ссылкам.

В программах с небольшим количеством нитей часто передают в качестве параметра указатели на статические переменные, но в больших программах с большим числом нитей это неприемлемо.

Завершение нити


Для завершения нити используется функция pthread_exit(3C). Эта функция всегда завершается успешно и принимает единственный параметр, указатель на код возврата типа void *. Как и в случае с pthread_create(3C), библиотека никогда не пытается обращаться к значению этого параметра как к указателю, поэтому его можно использовать для передачи скалярных значений.

Другой способ завершения нити – это возврат управления из функции start_routine при помощи оператора return. Поскольку функция start_routine должна возвращать тип void *, все ее операторы return должны возвращать значение. Этот способ практически полностью эквивалентен вызову pthread_exit(3C) с тем же самым значением.

Примечание
При компиляции программ на С++ некоторыми версиями компилятора GCC, при завершении нити вызовом pthread_exit(3C) не вызываются деструкторы локальных переменных. Информацию об этом не всегда удается найти в документации. В действительности это определяется не столько версией компилятора, сколько опциями при сборке компилятора и особенностями C runtime/libpthread.
Так, компилятор Sun Studio 11 вызывает деструкторы, а GCC 3.4.3, входящий в поставку Solaris 10 – не вызывает.
GCC 2.95.4 из поставки Debian Woody не вызывает деструкторы, GCC 3.3.5 из поставки Debian Sarge – вызывает.
Протестировать взаимодействие POSIX Thread API и вашего компилятора C++ можно при помощи программы из примера 2.

Пример 2


Примечание 2

Завершение процесса системным вызовом exit(2) или возвратом из функции main приводит к завершению всех нитей процесса. Это поведение описано в стандарте POSIX, поэтому ОС, которые ведут себя иначе (например, старые версии Linux), не соответствуют стандарту. Если вы хотите, чтобы нити вашей программы продолжали исполнение после завершения main, следует завершать main при помощи вызова pthread_exit(3C).

Поведение деструкторов локальных переменных в нитях при завершении процесса при помощи exit(2) не описано в стандарте. На практике они не вызываются даже для переменных, описанных в основной нити программы, и даже в однопоточных программах (проверялось в Solaris и Linux на всех доступных версиях компиляторов).

В действительности, все еще хуже – при выходе по exit(2) начинают вызываться деструкторы статических и глобальных переменных, но нити продолжают работу – в некоторых случаях это может приводить к аварийному завершению программы. Так, если в программе примера 2 в строке 42 закомментировать pthread_exit, то программа, собранная Sun Studio 11, с высокой вероятностью завершается по Segmentation Fault (попытка вывода в std::cout после того, как деструктор std::cout уже отработал). Поэтому если вы хотите экстренно завершить многопоточную программу, написанную на C++, используйте _exit(2). Если вам необходимо обеспечить вызов деструкторов локальных переменных, следует использовать исключения С++.

Примечание 3

К передаче выходного значения нити относятся все те же самые соображения, что и к передаче параметров нити. Хотя выходное значение нити описано как void *, библиотека никогда не обращается к нему как к указателю, поэтому в качестве значения можно возвращать ноль или другие скалярные значения.

При передаче указателей возникают те же проблемы, что и при передаче параметров – опасность возникновения висячих ссылок и опасность утечки памяти. Ни в коем случае нельзя возвращать указатели на локальные переменные и другие объекты, созданные в стеке нити, потому что нить, получающая возвращаемое значение функцией pthread_join(3C) (рассматривается ниже), получает доступ к значению лишь после того, как стек нити и другие ресурсы, связанные с нитью, в том числе thread local data, уже уничтожены.
При использовании для размещения возвращаемого значения malloc(3C) возникает опасность утечки памяти. Действительно, если для размещения значения использовать malloc(3C), то нить, получающая наше значение, должна его освободить. Но если эта нить получит сигнал принудительного завершения (pthread_cancel(3C)), то она не дождется нашего завершения и не освободит наше значение.

Универсального решения этой проблемы не существует. В программах с фиксированным или ограниченным числом нитей часто используют для передачи параметров и выходных значений указатели на статические переменные. В программах с переменным числом нитей обычно используют структуру, когда одна нить создает все остальные нити, размещает для них блоки переменных состояния, дожидается завершения каждой из нитей и уничтожает или переиспользует эти блоки. При этом, разумеется, логично, чтобы нить получала указатель на свой блок переменных состояния как параметр, работала с ним в течении всего срока жизни и возвращала этот же блок в качестве параметра pthread_exit(3C). Но эта архитектура пригодна не для всех приложений.

Ожидание завершения нити


Для ожидания завершения нити и получения ее кода возврата используется библиотечная функция pthread_join(3C). Эта функция имеет два параметра, идентификатор нити типа pthread_t, и указатель на переменную типа void *, в которой размещается значение кода возврата. Если в качестве второго параметра передать нулевой указатель, код возврата игнорируется.

Если требуемая нить еще не завершилась, то нить, сделавшая вызов pthread_join(3С), блокируется. Если такой нити (уже) не существует, pthread_join(3C) возвращает ошибку ESRCH.

Когда нить завершается, то связанные с ней ресурсы существуют до того момента, пока какая-то другая нить не вызовет pthread_join(3C). Однако к тому моменту, кода pthread_join завершается, все ресурсы, занятые нитью (стек, thread local data, дескриптор нити) уничтожаются.

В отличие от процессов Unix, где системный вызов wait(2) может использовать только родитель по отношению к потомкам, любая нить может ждать завершения любой другой нити того же процесса. Если несколько нитей ждут завершения одной и той же нити, которая еще не завершилась, все эти нити блокируются. Однако при завершении нити одна из ожидавших нитей получает код возврата, а остальные ошибку ESRCH.

Если нить пытается ожидать сама себя, она получает ошибку EDEADLK.

Еще одна важная функция, связанная с ожиданием завершения нити – это функция pthread_detach(3C). Эта функция указывает, что все ресурсы, связанные с нитью, необходимо уничтожать сразу после завершения этой нити. При этом уничтожается и код возврата такой нити – при попытке сделать pthread_join(3C) на нить, над которой перед этим сделали pthread_detach(3C), возвращается код ошибки EINVAL.

В руководстве по pthread_detach(3C) в системе Solaris 10 сказано, что главное применение pthread_detach(3C) – это ситуация, когда родитель, ожидавший завершения дочерней нити, получает pthread_cancel(3C). В действительности, существуют и другие применения «отсоединенных» нитей.

Не обязательно делать pthread_detach(3C) на уже запущенную нить; в атрибутах нити (pthread_attr_t) можно указать, что нить следует запускать в уже «отсоединенном» состоянии. Это рассматривается в следующей лекции.

Разумеется, нить, над которой сделали pthread_detach(3C), не должна выделять память под возвращаемое значение при помощи malloc(3C) – ведь никто не сможет освободить эту память и это приведет к утечке памяти.

Рекомендованного стандартом способа проверить собственную «отсоединенность» нет. Из предыдущего описания можно предположить, что для проверки «отсоединенности» можно было бы использовать код возврата pthread_join(3C) для собственного идентификатора нитей – для «отсоединенных» нитей это должен быть EINVAL, а для «неотсоединенных» – EDEADLK. Для Solaris 10 и Linux 2.6 это действительно так (во всяком случае для Debian Sarge), однако в Linux 2.4 ptread_join(pthread_self(), NULL) всегда возвращает EDEADLK. Как ведет себя pthread_join на вашей системе, можно проверить при помощи программы примера 3.

Пример 3

 
Файлов нет. [Показать файлы/форму]
Комментариев нет. [Показать комментарии/форму]