Блокировки по чтению/записи
Блокировки по чтению/записи (reader/writer locks) (или более точное название "блокировки на множественное чтение и однократную запись") используются в тех случаях, когда доступ к структуре данных должен определяться по следующей схеме: чтение данных выполняет множество потоков, запись — не более одного потока.
Блокировка по чтению/записи (pthread_rwlock_rdlock()) предоставляет доступ по чтению всем потокам, которые его запрашивают. Однако если поток запрашивает блокировку по записи (pthread_rwlock_wrlock()), запрос отклоняется до тех пор, пока все потоки, выполняющие чтение, не снимут свои блокировки по чтению (pthread_rwlock_unlock()).
Множество потоков, выполняющих запись, выстраиваются в очередь (в порядке своих приоритетов), ожидая возможности выполнить операцию записи в защищенную структуру данных. Все блокированные потоки, выполняющие запись, запускаются до того, как читающие потоки снова получат разрешение на доступ к данным. Приоритеты читающих потоков не учитываются.
Существуют специальные вызовы, которые позволяют потоку тестировать возможность доступа к необходимой блокировке, оставаясь в активном состоянии (pthread rwlockt ryrdlock() и ptfiread_rwlock_trywrlock()). Эти вызовы возвращают код завершения, сообщающий о возможности или невозможности установки блокировки.
Реализация блокировок по чтению/записи происходит не в ядре, а посредством мьютексов и условных переменных, предоставляемых ядром.
Семафоры
Еще одним средством синхронизации являются семафоры (semaphores), которые позволяют потокам увеличивать (с помощью функции sem_post()) или уменьшать (с помощью функции sem_wait()) значение счетчика на семафоре для управления блокировкой потока (операции "post" и "wait" соответственно).
При вызове функции sem_wait() поток не блокируется, если счетчик имел положительное значение. При неположительном значении счетчика поток блокируется до тех пор, пока какой-либо другой поток не увеличит значение счетчика. Увеличение значения счетчика может выполняться несколько раз подряд, что позволяет уменьшать значение счетчика, не вызывая блокировки потоков.
Существенным отличием семафоров от других примитивов синхронизации является то, что семафоры безопасны для применения в асинхронной среде ("async safe") и могут управляться обработчиками сигналов. Семафоры как раз подходят для тех случаев, когда требуется пробудить поток с помощью обработчика сигнала.
Другим полезным свойством семафоров является то, что они были определены для работы между процессами. Хотя мьютексы в QNX Neutrino тоже работают между процессами, стандарты POSIX рассматривают эту возможность как дополнительную функцию, которая может оказаться не переносимой между системами.
Полезной разновидностью семафоров является служба именованных семафоров (named semaphore service). Эта служба использует администратор ресурсов и позволяет применять семафоры между процессами, выполняемыми на разных машинах внутри сети.
Поскольку семафоры, как и условные переменные, могут в штатном порядке возвращать ненулевое значение из-за ложного пробуждения, для их корректной работы требуется использование цикла:
while (sem wait(&s) && errno == EINTR) {
do_nothing();
}
do_critical_region();/* Значение семафора уменьшилось. */
Синхронизация с помощью алгоритма планирования
Применение алгоритма FIFO-планирования стандарта POSIX в системе без симметричной многопроцессорной обработки предотвращает выполнение критической секции кода одновременно несколькими потоками с одинаковым приоритетом. Алгоритм FIFO-планирования предписывает, что все потоки, запланированные к выполнению по этому алгоритму и имеющие одинаковый приоритет, выполняются до тех пор, пока они самостоятельно не освободят процессор для другого потока.
Такое "освобождение" процессора также может произойти в случае, когда поток блокируется в результате обращения к другому процессу за сервисом или при получении сигнала. Поэтому критическая секция кода должна быть тщательно проработана и документирована для того, чтобы последующее обслуживание этого кода не приводило к нарушению данного условия. Кроме того, потоки с более высоким приоритетом в том (или любом другом) процессе все же могут вытеснять потоки, выполняемые по алгоритму FIFO-планирования. Поэтому все потоки, которые могут "столкнуться" между собой внутри критической секции кода, должны планироваться по алгоритму FIFO с одинаковым приоритетом. При таком условии потоки могут обращаться к этой разделяемой памяти без необходимости делать предварительный явный запрос на синхронизацию.
Метод монопольного доступа неприменим в многопроцессорных системах, поскольку в таких системах несколько процессоров могут одновременно исполнить код, который в однопроцессорной машине был бы запланирован на последовательное исполнение.
Дата добавления: 2017-01-29; просмотров: 1158;