Этот раздел содержит обзор библиотеки нитей и основных принципов создания программ с несколькими нитями. На протяжении всего раздела считается, если не указано иное, что работа с библиотекой нитей происходит в рамках одного процесса.
Более подробно создание программ с нитями описывается в следующих разделах:
Основные операции с нитями - это создание нити Создание нити и завершение работы нити Завершение работы нити.
Создание нитей отличается от создания процессов тем, что между нитями не существует "родственных" отношений (предок-потомок). Все нити, за исключением главной нити, автоматически создаваемой при создании процесса, находятся на одном и том же уровне иерархии. Для нити не ведется список порожденных нитей; кроме того, нить не знает, какая нить ее породила.
При создании нити необходимо указать процедуру точки входа и аргумент. Каждой нити соответствует процедура точки входа с одним аргументом. Несколько нитей могут работать с одной и той же процедурой точки входа. Дополнительная информация о создании нитей приведена в разделе Создание нитей.
Нити могут завершать свою работу двумя способами: путем возврата из процедуры точки входа и путем вызова библиотечной функции. Кроме того, нити могут завершать работу других нитей с помощью механизма остановки. Любая нить может отправить запрос на завершение работы другой нити. Любая нить может разрешить или запретить завершение своей работы по запросу другой нити. Для обработки запросов на завершение работы нитей можно также создать программы-обработчики очистки. Дополнительная информация о завершении работы нити приведена в разделе Завершение работы нитей.
Для обеспечения эффективного взаимодействия нитей необходимо синхронизировать их работу. Предусмотрены следующие средства синхронизации:
В библиотеке нитей реализовано три механизма синхронизации: взаимные блокировки, условные переменные и соединения. Эти простые, но мощные механизмы можно применять сами по себе, или составлять из них более сложные механизмы.
Взаимные блокировки позволяют предотвратить нарушение целостности данных вследствие так называемой "гонки". "Гонки" часто возникают в случае, когда несколько нитей должны выполнить действия в одной и той же области памяти, причем результат вычислений зависит от порядка действий.
Допустим, две нити (А и В) могут увеличивать значение счетчика Х. Если значение Х изначально было равно 1, то после приращения в обеих нитях его значение будет равно 3. Предположим, что нити А и В независимы друг от друга и не синхронизированы. Хотя оператор C X++ выглядит предельно просто и, казалось бы, должен быть атомарным, соответствующий ассемблерный код состоит из нескольких инструкций:
move X, REG inc REG move REG, X
Если нити А и В обрабатываются одновременно на двух процессорах, или если запланировано попеременное выполнение по одной инструкции из каждой нити, может произойти следующее:
Thread A Register = 1 Thread B Register = 1 Memory X = 1
Thread A Register = 2 Thread B Register = 2 Memory X = 1
Thread A Register = 2 Thread B Register = 2 Memory X = 2
Заметим, что в большинстве случаев нить А и нить В выполняют эти три инструкции единым блоком, и результат будет равен "очевидному" 3. Как правило, нарушение целостности данных вследствие гонки трудно обнаружить, так как гонка возникает не всегда.
Для того чтобы подобные ситуации не возникали, каждая нить должна блокировать данные перед обращением к счетчику и обновлением ячейки Х. Например, если нить А устанавливает блокировку и обновляет счетчик, то в момент снятия блокировки значение счетчика будет равно 2. После этого нить В устанавливает блокировку и обновляет счетчик еще раз, так что в результате значение Х будет равно 3.
Дополнительная информация о взаимных блокировках приведена в разделе Использование взаимных блокировок.
С помощью условных переменных можно заблокировать обработку нитей до наступления определенного события или выполнения определенного условия. Если программа удовлетворяет условию переменной, это отражается в виде булевского предиката. Степень сложности предиката условной переменной определяется программистом. Сигнал условия может генерироваться любой нитью и направляться либо одной, либо всем ожидающим нитям. Более подробную информацию вы найдете в разделе Использование условных переменных.
После завершения работы нити соответствующая память может не очищаться; это зависит от атрибутов нити. Такие нити можно соединять с другими нитями для получения доступа к оставшейся информации. Нить, к которой нужно подключить другую нить, блокируется до завершения работы подключенной нити. Механизм подключения представляет собой разновидность механизма условных переменных, причем условием является завершение работы нити. Дополнительная информация о стыковке приведена в разделе Стыковка нитей.
С помощью библиотеки нитей программист может управлять планированием обработки нитей. Существует несколько различных способов управления планированием:
Два последних способа управления называются планированием при синхронизации.
У нити есть три параметра
планирования:
Параметры планирования можно устанавливать как до создания нити, так и в ходе ее выполнения. Как правило, необходимо управлять параметрами планирования только для нитей, требующих много ресурсов процессора. Поэтому значения по умолчанию, устанавливаемые библиотекой нитей, приходится изменять достаточно редко. Дополнительная информация о работе с параметрами планирования нитей приведена в разделе Планирование работы нитей.
Планирование при синхронизации - это сложный механизм. В некоторых реализациях библиотеки нитей это средство отсутствует.
Планирование при синхронизации определяет изменение параметров планирования, в частности, приоритета, при взаимной блокировке. Это позволяет программисту управлять параметрами планирования и предотвратить изменение приоритетов, что весьма полезно при работе со сложными схемами блокировки. Дополнительная информация приведена в разделе Планирование при синхронизации.
В библиотеке нитей есть и другие полезные функции, с помощью которых программист может создавать сложные программы. Кроме того, библиотека нитей управляет взаимодействием нитей и процессов.
Библиотека нитей предоставляет API для работы с синхронизацией и планированием нитей. Кроме того, в библиотеке содержатся следующие средства:
Нити и процессы взаимодействуют путем выполнения специальных действий:
В этом разделе приведена общая информация об API библиотеки нитей. Эти сведения не требуются для написания программ с несколькими нитями, однако полезны для лучшего понимания работы API библиотеки нитей.
В API библиотеки нитей реализован объектно-ориентированный интерфейс. Программист работает с объектами с помощью указателей и других универсальных идентификаторов; детали реализации объекта скрыты от него. Благодаря этому обеспечивается переносимость программ с несколькими нитями для систем, поддерживающих эту библиотеку нитей. Кроме того, при переходе к более новой версии AIX достаточно просто повторно скомпилировать программу. Хотя определения некоторых типов данных содержатся в библиотечном файле заголовка (pthread.h), программы не должны работать с содержимым структур непосредственно, на основе этих определений, поскольку они зависят от реализации. Для работы с объектами всегда должны применяться стандартные процедуры библиотеки нитей.
Три основных типа объектов (непрозрачных типа данных), используемых в библиотеке нитей, - это нити, взаимные блокировки и условные переменные. У этих объектов есть атрибуты, задающие свойства объектов. При создании объекта необходимо задать его атрибуты. В библиотеке нитей эти атрибуты создания сами являются объектами, называемыми объектами атрибутов.
Следовательно, библиотека нитей работает с тремя типами пар объектов:
Для создания объекта необходимо создать соответствующий объект атрибутов. При создании объекта атрибутов их значения устанавливаются по умолчанию. Затем можно изменить значения отдельных атрибутов с помощью функций. Это позволяет гарантировать, что введение новых атрибутов и изменение их реализации не повлияет на программу с несколькими нитями. Следовательно, объект атрибутов можно использовать для создания одного или нескольких объектов, а затем уничтожить, причем уничтожение объекта атрибутов никак не отразится на объектах, созданных с его помощью.
Объекты атрибутов позволяют также работать с классами объектов. Для каждого класса объектов можно определить один объект атрибутов. Для создания экземпляра класса объекта следует создать такой объект с помощью объекта атрибутов класса.
Для идентификаторов, используемых библиотекой нитей, действует соглашение о
присвоении имен. Все идентификаторы библиотеки нитей начинаются с
префикса pthread_. Использовать этот префикс в
пользовательских именах нельзя. После префикса указывается имя
компонента. В библиотеке нитей определены следующие компоненты:
Идентификаторы типов данных оканчиваются символами _t. Имена функций и макросов заканчиваются символом подчеркивания _, после которого указывается имя, обозначающее действие, которое выполняет данная функция или макрос. Например, pthread_attr_init - идентификатор библиотеки нитей (pthread_), относящийся к объекту атрибутов нити (attr) и обозначающий функцию его инициализации (_init).
Явные имена макросов состоят из прописных букв. Однако некоторые функции могут быть реализованы как макросы, хотя их имена и состоят из строчных букв.
Реализация объектов pthread содержится в следующих файлах AIX:
/usr/include/pthread.h | Файл заголовка C/C++, в котором содержится большинство определений объектов pthread. |
/usr/include/sched.h | Файл заголовка C/C++, содержащий некоторые определения системы планирования. |
/usr/include/unistd.h | Файл заголовка C/C++, содержащий определение функции pthread_atfork(). |
/usr/include/sys/limits.h | Файл заголовка C/C++, содержащий определения некоторых объектов pthread. |
/usr/include/sys/pthdebug.h | Файл заголовка C/C++, в котором содержится большинство определений отладочных объектов pthread. |
/usr/include/sys/sched.h | Файл заголовка C/C++, содержащий некоторые определения системы планирования. |
/usr/include/sys/signal.h | Файл заголовка C/C++, в котором содержатся определения функций pthread_kill() и pthread_sigmask(). |
/usr/include/sys/types.h | Файл заголовка C/C++, содержащий определения некоторых объектов pthread. |
/usr/lib/libpthreads.a | 32/64-разрядная библиотека объектов pthread для стандартов UNIX98 и POSIX 1003.1c. |
/usr/lib/libpthreads_compat.a | 32-разрядная библиотека объектов pthread для проекта 7 стандарта POSIX 1003.1c. |
/usr/lib/profiled/libpthreads.a | Оптимизированная 32/64-разрядная библиотека объектов pthread для стандартов UNIX98 и POSIX 1003.1c. |
/usr/lib/profiled/libpthreads_compat.a | Оптимизированная 32-разрядная библиотека объектов pthread для проекта 7 стандарта POSIX 1003.1c. |
Глава 9, Параллельное программирование
Написание реентерабельных программ и программ с защитой нитей