- •Часть 4. Локальное взаимодействие процессов
- •Глава 16. Блокирование записей 89
- •12.2. Процессы, потоки и общий доступ к информации
- •12.3. Живучесть объектов ipc
- •12.4. Пространства имен
- •12.5. Действие команд fork, exec и exit на объекты ipc
- •12.6. Комментарии к примерам ipc
- •12.7. Выводы по главе 12
- •12.8. Упражнения по главе 12
- •Глава 13. Именованные и неименованные каналы
- •13.1. Введение
- •13.2. Приложение типа клиент-сервер
- •13.3. Программные каналы
- •13.4. Функции popen и pclose
- •13.5. Именованные каналы (fifo)
- •13.6. Некоторые свойства именованных и неименованных каналов
- •13.7. Один сервер, несколько клиентов
- •13.8. Последовательные и параллельные серверы
- •13.9. Ограничения программных каналов и fifo
- •13.10. Выводы по главе 13
- •13.11. Упражнения по главе 13
- •Глава 14. Программные потоки
- •14.1. Введение
- •14.2. Концепция потоков
- •14.3. Идентификация потоков
- •14.4. Создание потока
- •14.5. Завершение потока
- •Функции управления процессами и потоками
- •14.6. Установка атрибутов потока
- •14.7. Реентерабельность
- •Альтернативные версии функций, безопасные в многопоточной среде
- •14.8. Локальные данные потоков
- •14.9. Принудительное завершение потоков
- •Некоторые точки выхода, определенные стандартом Posix.1
- •14.10. Потоки и сигналы
- •14.11. Выводы по главе 14
- •14.12. Упражнения по главе 14 Глава 15. Средства синхронизации потоков
- •15.1. Введение
- •15.2. Взаимные исключения: установка и снятие блокировки
- •15.2.1. Схема производитель-потребитель
- •15.2.2. Блокирование и опрос
- •15.2.3. Предотвращение тупиковых ситуаций
- •15.3. Условные переменные
- •15.3.1. Ожидание и сигнализация
- •15.3.2. Исключение состояния гонок
- •15.4. Блокировки чтения-записи
- •15.5. Атрибуты средств синхронизации потоков
- •15.5.1. Атрибуты взаимных исключений
- •Поведение взаимных исключений различных типов
- •15.5.2. Атрибуты условных переменных
- •15.5.3. Атрибуты блокировок чтения-записи
- •15.6. Выводы по главе 15
- •15.7. Упражнения по главе 15
- •Глава 16. Блокирование записей
- •16.1. Введение
- •16.2. Блокирование записей и файлов
- •16.3. Блокирование записей с помощью fcntl по стандарту Posix
- •16.4. Рекомендательная блокировка
- •16.5. Обязательная блокировка
- •16.6. Приоритет чтения и записи Выводы по главе 16
- •Упражнения по главе 16 Глава 17. System V ipc
- •17.1. Введение
- •17.2. Ключи типа key_t и функция ftok
- •17.3. Структура ipc_perm
- •17.4. Создание и открытие каналов ipc
- •17.5. Разрешения ipc
- •17.6. Программы ipcs и ipcrm
- •17.7. Ограничения ядра
- •17.8. Выводы по главе 17
- •17.9. Упражнения по главе 17
- •Глава 18. Очереди сообщений System V
- •18.1. Введение
- •18.2. Функция msgget
- •18.3. Функция msgsnd
- •18.4. Функция msgrcv
- •18.5. Функция msgctl
- •18.6. Пример программы клиент-сервер
- •18.7. Мультиплексирование сообщений
- •18.7.1. Пример: одна очередь на приложение
- •18.7.2. Пример: одна очередь для каждого клиента
- •18.8. Ограничения, накладываемые на очереди сообщений
- •18.9. Выводы по главе 18
- •18.10. Упражнения по главе 18
- •Глава 19. Семафоры System V
- •19.1. Введение
- •19.2. Функция semget
- •19.3. Функция semop
- •19.4. Функция semctl
- •19. . Ограничения семафоров System V
- •19. . Выводы по главе 19
- •19. . Упражнения по главе 19 Глава 20. Введение в разделяемую память
- •20.1. Введение
- •20.2. Функции mmap, munmap и msync
- •20.3. Увеличение счетчика в отображаемом в память файле
- •20.4. Неименованное отображение в память
- •20.5. Обращение к объектам, отображенным в память
- •20.6. Выводы по главе 20
- •20.7. Упражнения по главе 20
- •Глава 21. Разделяемая память System V
- •21.1. Введение
- •21.2. Функция shmget
- •21.3. Функция shmat
- •21.4. Функция shmdt
- •21.5. Функция shmctl
- •21.6. Ограничения, накладываемые на разделяемую память
- •21.7. Выводы по главе 21
- •21.8. Упражнения по главе 21
15.2. Взаимные исключения: установка и снятие блокировки
Взаимное исключение является простейшей формой синхронизации. Оно используется для защиты критической области (critical region) программы, предотвращая одновременное выполнение участка кода несколькими потоками (если взаимное исключение используется потоками) или процессами (если взаимное исключение используется несколькими процессами). Взаимное исключение – это фактически блокировка, которая устанавливается (запирается) перед обращением к разделяемому ресурсу и снимается (отпирается) после выполнения требуемой совокупности операций. Выглядит это обычно следующим образом:
запереть_взаимное_исключение(...);
критическая_область
отпереть_взаимное_исключение(...);
Если взаимное исключение находится в запертом состоянии, то любой другой поток, который попытается отпереть его, будет заблокирован до тех пор, пока взаимное исключение не будет отперто. Если в момент, когда отпирается взаимное исключение, заблокированными окажутся несколько потоков, все они будут разблокированы и тот из них, который первым успеет запереть взаимное исключение, продолжит работу. Все остальные потоки обнаружат, что взаимное исключение по-прежнему заперто, и они опять вернутся в режим ожидания. Это гарантирует, что только один поток будет выполнять код, относящийся к критической области.
Взаимные исключения по стандарту Posix.1 объявляют как переменные с типом данных pthread_mutex_t.Прежде чем использовать переменную-исключение, мы должны сначала инициализировать ее, присвоив ей значение константы PTHREAD_MUTEX_INITIALIZER (только для статически размещаемых переменных-исключений) или вызвав функцию pthread_mutex_init. Если взаимное исключение размещается в динамической памяти (например, с помощью функции malloc), то прежде чем освободить занимаемую память, необходимо вызвать функцию pthread_mutex_destroy.
#include <pthread.h>
int pthread_mutex_init (pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy (pthread_mutex_t *mutex);
/* обе функции возвращают 0 в случае успеха, код ошибки – в случае неудачи */
Чтобы инициализировать взаимное исключение со значениями атрибутов по умолчанию, мы должны передать пустой указатель (NULL) в аргументе attr. Конкретные значения атрибутов взаимных исключений мы рассмотрим в разделе 12.4.
Для запирания взаимного исключения следует вызвать функцию pthread_mutex_lock. Если взаимное исключение уже установлено, вызывающий поток будет заблокирован до тех пор, пока взаимное исключение не будет отперто. Взаимное исключение отпирается с помощью функции pthread_mutex_unlock.
#include <pthread.h>
int pthread_mutex_lock (pthread_mutex_t *mutex);
int pthread_mutex_trylock (pthread_mutex_t *mutex);
int pthread_mutex_unlock (pthread_mutex_t *mutex);
/* все три функции возвращают 0 в случае успеха, код ошибки – в случае неудачи */
Если поток не должен оказаться заблокированным при попытке запереть взаимное исключение, он может воспользоваться функцией pthread_mutex_trylock. Если к моменту вызова этой функции взаимное исключение будет отперто, функция запрет взаимное исключение и вернет значение 0, иначе функция pthread_mutex_trylock вернет код ошибки EBUSY.
Хотя мы говорим о защите критической области кода программы, на самом деле речь идет о защите данных, с которыми работает эта часть кода. Другими словами, взаимное исключение обычно используется для защиты совместно используемых несколькими потоками или процессами данных.
Описанный механизм взаимных исключений будет корректно работать только при условии, что все потоки внутри программы будут соблюдать одни и те же правила доступа к данным. Операционная система в данном случае никак не упорядочивает доступ к данным. Взаимные исключения представляют собой блокировку коллективного пользования. Это значит, что если совместно используемые данные представляют собой связный список, то все потоки, работающие с этим списком, должны блокировать взаимное исключение. Если мы позволим одному потоку производить действия с разделяемыми данными, предварительно не ограничив доступ к ним, то остальные потоки могут обнаружить эти данные в противоречивом состоянии, даже если перед обращением к ним они будут устанавливать блокировку. Взаимные исключения предполагают добровольное сотрудничество потоков.
