Перейти на главную страницу
Упрощенно вызов pthread_create(&thr,NULL,start,NULL) создаст поток, который начнет выполнять функцию start и запишет в переменную thr идентификатор созданного потока.
Первый аргумент этой функции, thr — это указатель на переменную типа pthread_t, в которую будет записан идентификатор созданного потока, который в последствии можно будет передавать другим вызовам, когда потребуется сделать что-либо с этим потоком.
Здесь наблюдается особенность POSIX API, а именно с непрозрачность базовых типов. Дело в том, что практически ничего нельзя сказать про тип pthread_t. Единственное что сказано в стандарте, это что эти значения можно копировать, и что используя вызов int pthread_equal(pthread_t thr1, pthread_t thr2), можно установить что оба идентификатора thr1 и thr2 идентифицируют один и тот же поток (при этом они вполне могут быть неравны в смысле оператора равенства).
Второй аргумент функции pthread_create, attr - указатель на переменную типа pthread_attr_t, которая задает набор свойств создаваемого потока. Это вторая особенность POSIX API, а именно концепция атрибутов. Дело в том, что в этом API во всех случаях, когда при создании или инициализации некоторого объекта необходимо задать набор неких дополнительных его свойств, вместо указания этого набора при помощи набора параметров вызова используется передача предварительно сконструированного объекта набора атрибутов.
Такое решение имеет, по крайней мере, два преимущества. Во-первых, можно зафиксировать набор параметров функции без угрозы его изменения в дальнейшем, когда у этого объекта появятся новые свойства. Во-вторых, можно многократно использовать один и тот же набор атрибутов для создания множества объектов.
Третий аргумент вызова pthread_create - это указатель на функцию типа void* ()(void *). Именно эту функцию и начинает выполнять вновь созданный поток, при этом в качестве параметра этой функции передается четвертый аргумент вызова pthread_create. Таким образом, можно с одной стороны параметризовать создаваемый поток кодом, который он будет выполнять, с другой стороны параметризовать его различными данными, передаваемыми коду.
Функция pthread_create возвращает нулевое значение в случае успеха и ненулевой код ошибки в случае неудачи. Это также одна из особенностей POSIX API, вместо стандартного для Unix подхода, когда функция возвращает лишь некоторый индикатор ошибки, а код ошибки устанавливает в переменной errno. Функции POSIX API возвращают код ошибки в результате своего аргумента.
В качестве резюме рассмотрим пример, thread1.c, который можно скачать с сайта в папке «Threads».
Хотя функции работы с потоками описаны в файле включения pthread.h, на самом деле они находятся в библиотеке libgcc.a. Поэтому процесс компиляции и сборки многопоточной программы выполняется в два этапа:
В большинстве версий Linux библиотека лежит в /usr/lib/.
Эта функция дожидается завершения потока с идентификатором thread, и записывает возвращаемое ею значение в переменную, на которую указывает value_ptr. При этом освобождаются все ресурсы, связанные с потоком, потому эта функция может быть вызвана для данного потока только один раз.
Если возврат значения через pthread_join не удобен, например, необходимо получить данные из нескольких потоков, то следует воспользоваться каким либо другим механизмом, например, организовать очередь возвращаемых значений, или возвращать значение в структуре, указатель на которую передают в качестве параметра потока. То есть использование pthread_join - это вопрос удобства, а не догма, в отличие от случая пары fork() - wait().
В том случае, если нужно использовать другой механизм возврата или возвращаемое значение просто не интересует, то можно отсоединить (detach) поток, сказав тем самым, что нужно освободить ресурсы, связанные с потоком, сразу по завершению функции потока. Сделать это можно несколькими способами.
Во-первых, можно сразу создать поток отсоединенным, задав соответствующий объект атрибутов при вызове pthread_create.
Во-вторых, любой поток можно отсоединить, вызвав в любой момент его жизни (то есть до вызова pthread_join()) функцию int pthread_detach(pthread_t thread), и указав ей в качестве параметра идентификатор потока. При этом поток вполне может отсоединить сам себя, получив свой идентификатор при помощи функции pthread_t pthread_self(void). Следует подчеркнуть, что отсоединение потока никоим образом не влияет на процесс его выполнения, а просто помечает поток как готовый по своему завершению к освобождению ресурсов.
Под освобождаемыми ресурсами подразумеваются в первую очередь стек, память, в которую сохраняется контекст потока, данные, специфичные для потока и тому подобное. Сюда не входят ресурсы, выделяемые явно, например, память, выделяемая через malloc, или открываемые файлы. Подобные ресурсы следует освобождать явно.
Помимо возврата из функции потока существует вызов, аналогичный вызову exit() для процессов:
Этот вызов завершает выполняемый поток, возвращая в качестве результата его выполнения value_ptr. При вызове этой функции поток из нее просто не возвращается. Нужно помнить, что функция exit() по-прежнему завершает процесс, то есть уничтожает все потоки.
Рассмотрим соответствующий пример, thread2.c.
Для досрочного завершения потока можно воспользоваться функцией pthread_cancel(pthread_t thread). Единственным ее аргументом является идентификатор потока thread. Функция pthread_cancel() возвращает 0 в случае успеха и ненулевое значение в случае ошибки. Хотя pthread_cancel() может завершить поток досрочно, ее нельзя назвать средством принудительного завершения потоков, поскольку поток может не только самостоятельно выбрать порядок завершения, но и игнорировать этот вызов. Вызов функции pthread_cancel() следует рассматривать как запрос на выполнение досрочного завершения потока. Функция pthread_setcancelstate() определяет, будет ли поток реагировать на обращение к нему с помощью pthread_cancel(), или не будет. У функции pthread_setcancelstate() два параметра, параметр state типа int и параметр oldstate типа *int. В первом передается значение, указывающее, как поток должен реагировать на запрос pthread_cancel(), а в переменную, чей адрес был передан во втором параметре, функция записывает прежнее значение. Если прежнее значение не интересует, во втором параметре можно передать NULL.
Чаще всего функция pthread_setcancelstate() используется для временного запрета завершения потока. Если в потоке есть участок кода, во время выполнения которого завершать поток крайне нежелательно, то можно оградить этот участок кода от досрочного завершения с помощью пары вызовов pthread_setcancelstate():
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
... //Здесь поток завершать нельзя
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
Первый вызов pthread_setcancelstate() запрещает досрочное завершение потока, второй – разрешает. Если запрос на досрочное завершение потока поступит в тот момент, когда поток игнорирует эти запросы, выполнение запроса будет отложено до тех пор, пока функция pthread_setcancelstate() не будет вызвана с аргументом PTHREAD_CANCEL_ENABLE.
Интересна роль функции pthread_testcancel(void).Эта функция создает точку отмены потока. Даже если досрочное завершение разрешено, поток, получивший запрос на досрочное завершение, может завершить работу не сразу. Если поток находится в режиме отложенного досрочного завершения (а этот режим установлен по умолчанию), он выполнит запрос на досрочное завершение, только достигнув одной из точек отмены. В соответствии со стандартом POSIX, точками отмены являются вызовы многих «обычных» функций, например open(), pause() и write(). Про функцию printf() в документации сказано, что она может быть точкой отмены, но в Linux при попытке остановиться на printf() поток завершается, а pthread_join() не возвращает управления. Поэтому создается явная точка отмены с помощью вызова pthread_testcancel().
Можно выполнить досрочное завершение потока, не дожидаясь точек останова. Для этого необходимо перевести поток в режим немедленного завершения с помощью вызова pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL). В этом случае беспокоиться о точках останова уже не нужно. Вызов pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) снова переводит поток в режим отложенного досрочного завершения.
Возврат из этого потока завершает весь процесс. Если не надо, чтобы по завершении этого потока остальные потоки были уничтожены, то следует воспользоваться функцией pthread_exit.
У функции этого потока не один параметр типа void*, как у остальных, а пара argc-argv.
Многие реализации отводят на стек начального потока больше памяти, чем на стеки остальных потоков. Это связано с тем, что существует много традиционных, однопоточных приложений, требующих значительного объема стека.
Любой создаваемый поток начинает свою жизнь в состоянии "готов". После чего в зависимости от политики планирования системы он может либо сразу перейти в состояние "выполняется", либо перейти в него через некоторое время.
Типичной ошибкой является считать, что (в отсутствии явных мер по синхронизации потоков) после возврата из функции pthread_create новый поток будет существовать. Но при некоторых политиках планирования и атрибутах потока может статься, что новый поток успеет выполниться к моменту возврата из этой функции.
Таблица 3.9. Состояния потока.
Состояние потока |
Что означает |
Готов (Ready) |
Поток готов к выполнению, но ожидает процессора. Возможно, он только что был создан, был вытеснен с процессора другим потоком, или только что был разблокирован. |
Выполняется (Running) |
Поток сейчас выполняется. Следует заметить, что на многопроцессорной машине может быть несколько потоков в таком состоянии. |
Заблокирован (Blocked) |
Поток не может выполняться, так как ожидает чего-либо. Например, окончания операции ввода-вывода. |
Завершен (Terminated) |
Поток была завершен, например, вследствие возврата из функции потока, вызова pthread_exit. Поток не был отсоединен и для него не была вызвана функция pthread_join. Как только происходит одно из этих событий, поток перестает существовать. |
Обычно вызываются три функции:
Объект атрибутов является закрытым и не может быть изменен операциями присваивания. Как только атрибут инициализируется и конфигурируется, он доступен всему процессу. Поэтому рекомендуется конфигурировать все требуемые спецификации состояния один раз на ранних стадиях выполнения программы. При этом соответствующий объект атрибутов может использоваться везде, где это нужно.
Использование объектов атрибутов имеет два преимущества:
Пример вызова функции:
Функция возвращает 0 после успешного завершения. Любое другое значение указывает, что произошла ошибка. Код ошибки устанавливается в переменной errno.
Значения по умолчанию для атрибутов приведены в таблице.
Таблица 3.10. Атрибуты потоков.
Атрибут |
Значение по умолчанию |
Назначение |
detachstate |
PTHREAD_CREATE_JOINABLE |
Управление состоянием потока (присоединяемый PTHREAD_CREATE_JOINABLE /отсоединяемый PTHREAD_CREATE_DEACHED) |
schedpolicy |
SCHED_OTHER |
Выбор политики диспетче-ризации: SCHED_OTHER (non-realtime), SCHED_RR (realtime) или SCHED_FIFO (realtime) |
schedparam |
0 |
Приоритет при диспетчеризации, имеет смысл только для SCHED_RR и SCHED_FIFO |
inheritsched |
PTHREAD_EXPLICIT_SCHED |
Параметры диспетчеризации задаются или наследуются от родительского потока (PTHREAD_ INHERIT_SCHED) |
scope |
PTHREAD_SCOPE_SYSTEM. |
PTHREAD_SCOPE_SYSTEM – поток конкурирует за процессор со всеми потоками системы. PTHREAD_SCOPE_PROCESS – поток конкурирует за процессор с потоками, созданными родительским потоком |
Функция возвращает 0 после успешного завершения или любое другое значение в случае ошибки.
Если поток создается отделенным (PTHREAD_CREATE_DETACHED), его PID и другие ресурсы могут использоваться, как только он завершится. Для этого можно вызвать перед его созданием функцию pthread_attr_setdetachstate(). По умолчанию поток создается неотделенным (PTHREAD_CREATE_JOINABLE), и предполагается, что создающий поток будет ожидать его завершения и выполнять pthread_join(). Независимо от типа потока, процесс не закончится, пока не завершатся все потоки. pthread_attr_setdetachstate() возвращает 0 после успешного завершения или другое значение в случае ошибки.
pthread_attr_setdetachstate(&tattr;,PTHREAD_CREATE_DETACHED);
Функция pthread_attr_getdetachstate() позволяет определить состояние при создании потока, т.е. был ли он отделенным или присоединяемым. Она возвращает 0 после успешного завершения или любое другое значение в случае ошибки.
Поток может быть неограничен (имеет тип PTHREAD_SCOPE_SYSTEM) или ограничен (имеет тип PTHREAD_SCOPE_PROCESS). Оба этих типа доступны только в пределах данного процесса. Функция pthread_attr_setscope() позволяет создать потоки указанных типов. pthread_attr_setscope() возвращает 0 после успешного завершения или любое другое значение в случае ошибки.
Функция pthread_attr_getscope() используется для определения ограниченности потока. pthread_att_getscope() возвращает 0 после успешного завершения или другое значение в случае ошибки.
ret = pthread_attr_getscope (&tattr;, &scope;);
Стандарт POSIX определяет значения атрибута планирования: SCHED_FIFO, SCHED_RR (Round Robin), или SCHED_OTHER (метод приложения). Дисциплины SCHED_FIFO и SCHED_RR поддерживаются только для потоков в режиме реального времени. Попытка установить их в других режимах приведет к возникновению ошибки ENOSUP.
Парная функция pthread_attr_getschedpolicy(), которая возвращает константу, определяющую дисциплину диспетчеризации.
Функция pthread_attr_setinheritsched() используется для наследования дисциплины диспетчеризации из родительского потока. Если атрибут inherit = PTHREAD_ INHERIT_SCHED (по умолчанию), то будет использована дисциплина планирования родителя. Если используется константа PTHREAD_EXPLICIT_SCHED, используются атрибуты, переданные в вызове pthread_create(). Функция возвращает 0 при успешном завершении, и любое другое значение в случае ошибки.
pthread_attr_setinheritsched(&tattr;,PTHREAD_EXPLICIT_SCHED);
Функцию pthread_attr_getinheritsched (&tattr, int *inherit) можно использовать для получения информации о дисциплине планирования текущего потока.
Параметры диспетчеризации определены в структуре sched_param; поддерживается только приоритет sched_param.sched_priority. Этот приоритет задается целым числом и чем выше значение, тем выше приоритет потока при планировании. Создаваемые потоки получают этот приоритет. Функция pthread_attr_setschedparam() используется, чтобы установить значения в этой структуре. При успешном завершении она возвращает 0.
sched_param param;
param.sched_priority = 20;
ret = pthread_attr_setschedparam (&tattr;, ¶m;);
Функция pthread_attr_getschedparam (pthread_attr_t *tattr, const struct sched_param *param) используется для получения приоритета текущего потока.
Системное программное обеспечение. Конспект лекций. – Новосибирск: Изд-во нгту, 2011.– 127 с
12 09 2014
14 стр.
Новые термины и понятия: программа, программное обеспечение, базовое программное обеспечение, системное программное обеспечение, служебное программное обеспечение, прикладное прогр
10 10 2014
1 стр.
Операционные системы", "Базы данных", "Информационные технологии", "Теория принятия решений", "Системное программное обеспечение"
30 09 2014
1 стр.
Если аппаратные средства тело компьютера тогда, программное обеспечение своя душа (душа). Программное обеспечение срок, относился к наборам инструкций, названных программами. Письм
14 12 2014
1 стр.
Программное обеспечение для создания полнотекстовых коллекций (электронных библиотек) гринстоун
15 10 2014
3 стр.
С точки зрения программиста сопроцессор представляет из себя множество регистров и набор команд, предназначенных для обработки собственных типов данных: три целых двоичных, один це
11 10 2014
1 стр.
Программа «Оборудование и программное обеспечение радиостудии» носит исключительно прикладной характер и являет своей целью предварительное знакомство студентов с оборудованием Уче
02 10 2014
1 стр.
Рабочая программа разработана в соответствии с государственным общеобязательным стандартом образования (госо-2006г.) на основе рабочего учебного плана специальности 050704 «Вычисли
15 09 2014
1 стр.