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

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

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


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

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

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

Возвращаемое значение
0 при успешном завершении
Код ошибки при неудачном завершении

Коды ошибок
Значения кодов ошибок определены в виде символов препроцессора в файле 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). Если вам необходимо обеспечить вызов деструкторов локальных переменных, следует использовать исключения С++.

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