[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]

Программирование: Разработка и отладка программ


Рекомендации по созданию программ с нитями

Этот раздел содержит обзор библиотеки нитей и основных принципов создания программ с несколькими нитями. На протяжении всего раздела считается, если не указано иное, что работа с библиотекой нитей происходит в рамках одного процесса.

Более подробно создание программ с нитями описывается в следующих разделах:

Основные операции

Основные операции с нитями - это создание нити Создание нити и завершение работы нити Завершение работы нити.

Создание нити

Создание нитей отличается от создания процессов тем, что между нитями не существует "родственных" отношений (предок-потомок). Все нити, за исключением главной нити, автоматически создаваемой при создании процесса, находятся на одном и том же уровне иерархии. Для нити не ведется список порожденных нитей; кроме того, нить не знает, какая нить ее породила.

При создании нити необходимо указать процедуру точки входа и аргумент. Каждой нити соответствует процедура точки входа с одним аргументом. Несколько нитей могут работать с одной и той же процедурой точки входа. Дополнительная информация о создании нитей приведена в разделе Создание нитей.

Завершение работы нити

Нити могут завершать свою работу двумя способами: путем возврата из процедуры точки входа и путем вызова библиотечной функции. Кроме того, нити могут завершать работу других нитей с помощью механизма остановки. Любая нить может отправить запрос на завершение работы другой нити. Любая нить может разрешить или запретить завершение своей работы по запросу другой нити. Для обработки запросов на завершение работы нитей можно также создать программы-обработчики очистки. Дополнительная информация о завершении работы нити приведена в разделе Завершение работы нитей.

Синхронизация

Для обеспечения эффективного взаимодействия нитей необходимо синхронизировать их работу. Предусмотрены следующие средства синхронизации:

В библиотеке нитей реализовано три механизма синхронизации: взаимные блокировки, условные переменные и соединения. Эти простые, но мощные механизмы можно применять сами по себе, или составлять из них более сложные механизмы.

Взаимные блокировки и "гонки"

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

Допустим, две нити (А и В) могут увеличивать значение счетчика Х. Если значение Х изначально было равно 1, то после приращения в обеих нитях его значение будет равно 3. Предположим, что нити А и В независимы друг от друга и не синхронизированы. Хотя оператор C X++ выглядит предельно просто и, казалось бы, должен быть атомарным, соответствующий ассемблерный код состоит из нескольких инструкций:

move    X, REG
inc     REG
move    REG, X

Если нити А и В обрабатываются одновременно на двух процессорах, или если запланировано попеременное выполнение по одной инструкции из каждой нити, может произойти следующее:

  1. Нить А выполняет первую инструкцию и помещает значение Х, равное 1, в регистр нити А. После этого нить В выполняет свою первую инструкцию, помещая то же значение 1 в регистр нити В. В примере показано содержимое регистров и ячейки памяти Х в результате этих действий.
    Thread A Register = 1
    Thread B Register = 1
    Memory X          = 1
    
  2. Затем нить А выполняет вторую инструкцию, увеличивая значение в своем регистре на 1, теперь это значение равно 2. После этого нить В делает то же самое со своим регистром. В ячейку памяти Х не записывается ничего, поэтому там остается значение 1. В примере показано содержимое регистров и ячейки памяти Х в результате этих действий.
    Thread A Register = 2
    Thread B Register = 2
    Memory X          = 1
    
  3. Наконец, нить А записывает значение из своего регистра, равное 2, в ячейку Х. Затем нить В записывает в эту ячейку то же значение 2 из своего регистра. Значение Х оказывается равным 2. В примере показано содержимое регистров и ячейки памяти Х в результате этих действий.
    Thread A Register = 2
    Thread B Register = 2
    Memory X          = 2
    

Заметим, что в большинстве случаев нить А и нить В выполняют эти три инструкции единым блоком, и результат будет равен "очевидному" 3. Как правило, нарушение целостности данных вследствие гонки трудно обнаружить, так как гонка возникает не всегда.

Для того чтобы подобные ситуации не возникали, каждая нить должна блокировать данные перед обращением к счетчику и обновлением ячейки Х. Например, если нить А устанавливает блокировку и обновляет счетчик, то в момент снятия блокировки значение счетчика будет равно 2. После этого нить В устанавливает блокировку и обновляет счетчик еще раз, так что в результате значение Х будет равно 3.

Дополнительная информация о взаимных блокировках приведена в разделе Использование взаимных блокировок.

Ожидание нитей

С помощью условных переменных можно заблокировать обработку нитей до наступления определенного события или выполнения определенного условия. Если программа удовлетворяет условию переменной, это отражается в виде булевского предиката. Степень сложности предиката условной переменной определяется программистом. Сигнал условия может генерироваться любой нитью и направляться либо одной, либо всем ожидающим нитям. Более подробную информацию вы найдете в разделе Использование условных переменных.

После завершения работы нити соответствующая память может не очищаться; это зависит от атрибутов нити. Такие нити можно соединять с другими нитями для получения доступа к оставшейся информации. Нить, к которой нужно подключить другую нить, блокируется до завершения работы подключенной нити. Механизм подключения представляет собой разновидность механизма условных переменных, причем условием является завершение работы нити. Дополнительная информация о стыковке приведена в разделе Стыковка нитей.

Планирование

С помощью библиотеки нитей программист может управлять планированием обработки нитей. Существует несколько различных способов управления планированием:

Два последних способа управления называются планированием при синхронизации.

Параметры планирования

У нити есть три параметра планирования:

Область Область действия нити определяется моделью обработки нитей, используемой библиотекой.
Стратегия Стратегия планирования нити определяет, как планировщик работает с нитью после передачи ей управления.
Приоритет Приоритет планирования нити определяет относительную степень важности ее выполнения.

Параметры планирования можно устанавливать как до создания нити, так и в ходе ее выполнения. Как правило, необходимо управлять параметрами планирования только для нитей, требующих много ресурсов процессора. Поэтому значения по умолчанию, устанавливаемые библиотекой нитей, приходится изменять достаточно редко. Дополнительная информация о работе с параметрами планирования нитей приведена в разделе Планирование работы нитей.

Планирование при синхронизации

Планирование при синхронизации - это сложный механизм. В некоторых реализациях библиотеки нитей это средство отсутствует.

Планирование при синхронизации определяет изменение параметров планирования, в частности, приоритета, при взаимной блокировке. Это позволяет программисту управлять параметрами планирования и предотвратить изменение приоритетов, что весьма полезно при работе со сложными схемами блокировки. Дополнительная информация приведена в разделе Планирование при синхронизации.

Другие функции

В библиотеке нитей есть и другие полезные функции, с помощью которых программист может создавать сложные программы. Кроме того, библиотека нитей управляет взаимодействием нитей и процессов.

Дополнительные средства

Библиотека нитей предоставляет API для работы с синхронизацией и планированием нитей. Кроме того, в библиотеке содержатся следующие средства:

Взаимодействие между нитями и процессами

Нити и процессы взаимодействуют путем выполнения специальных действий:

API библиотеки нитей

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

Объектно-ориентированный интерфейс

В API библиотеки нитей реализован объектно-ориентированный интерфейс. Программист работает с объектами с помощью указателей и других универсальных идентификаторов; детали реализации объекта скрыты от него. Благодаря этому обеспечивается переносимость программ с несколькими нитями для систем, поддерживающих эту библиотеку нитей. Кроме того, при переходе к более новой версии AIX достаточно просто повторно скомпилировать программу. Хотя определения некоторых типов данных содержатся в библиотечном файле заголовка (pthread.h), программы не должны работать с содержимым структур непосредственно, на основе этих определений, поскольку они зависят от реализации. Для работы с объектами всегда должны применяться стандартные процедуры библиотеки нитей.

Три основных типа объектов (непрозрачных типа данных), используемых в библиотеке нитей, - это нити, взаимные блокировки и условные переменные. У этих объектов есть атрибуты, задающие свойства объектов. При создании объекта необходимо задать его атрибуты. В библиотеке нитей эти атрибуты создания сами являются объектами, называемыми объектами атрибутов.

Следовательно, библиотека нитей работает с тремя типами пар объектов:

Для создания объекта необходимо создать соответствующий объект атрибутов. При создании объекта атрибутов их значения устанавливаются по умолчанию. Затем можно изменить значения отдельных атрибутов с помощью функций. Это позволяет гарантировать, что введение новых атрибутов и изменение их реализации не повлияет на программу с несколькими нитями. Следовательно, объект атрибутов можно использовать для создания одного или нескольких объектов, а затем уничтожить, причем уничтожение объекта атрибутов никак не отразится на объектах, созданных с его помощью.

Объекты атрибутов позволяют также работать с классами объектов. Для каждого класса объектов можно определить один объект атрибутов. Для создания экземпляра класса объекта следует создать такой объект с помощью объекта атрибутов класса.

Соглашение о присвоении имен

Для идентификаторов, используемых библиотекой нитей, действует соглашение о присвоении имен. Все идентификаторы библиотеки нитей начинаются с префикса pthread_. Использовать этот префикс в пользовательских именах нельзя. После префикса указывается имя компонента. В библиотеке нитей определены следующие компоненты:

pthread_ Сами нити и различные функции
pthread_attr Объекты атрибутов нитей
pthread_cond Условные переменные
pthread_condattr Объекты условных атрибутов
pthread_key Ключи данных для конкретных нитей
pthread_mutex Взаимные блокировки
pthread_mutexattr Объекты атрибутов взаимных блокировок

Идентификаторы типов данных оканчиваются символами _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, Параллельное программирование

Основные сведения о нитях

Написание реентерабельных программ и программ с защитой нитей


[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]