В дополнение к механизмам параллельных вычислений UNIX SVR4, Solaris поддерживает четыре примитива синхронизации потоков.
Блокировки взаимоисключений (mutex).
Семафоры.
Блокировки читатели/писатель (несколько читателей, один писатель).
Переменные условий.
Примитивы для потоков ядра в Solaris реализованы в самом ядре; для работы с пользовательскими потоками имеется соответствующая библиотека. Работа с примитивами осуществляется через структуры данных, содержащие определяемые создающим эти структуры потоком параметры. После создания синхронизирующего объекта с ним, по сути, могут выполняться только две операции: войти (захватить, заблокировать) и освободить (деблокировать). Ни в ядре, ни в библиотеке нет механизмов, обеспечивающих взаимоисключения или отсутствие взаимоблокировок.
Все примитивы синхронизации требуют наличия машинных команд, которые позволяют атомарно проверить и установить значение объекта (см. раздел 5.3).
Блокировки взаимоисключений
Блокировки взаимоисключений (мьютексы5) предотвращают одновременную работу нескольких потоков при захвате блокировки. Поток, блокировавший работу остальных потоков (посредством вызова примитива mutex_enter), должен и деблокировать их (посредством вызова
примитива mutex_exit). Если примитив mutex_enter не в состоянии установить блокировку (поскольку она уже установлена другим потоком), то его дальнейшие действия зависят от информации из структуры данных блокировки. По умолчанию поток циклически опрашивает состояние блокировки, однако может применяться и механизм прерываний с очередью заблокированных потоков.
Для работы с блокировками используются следующие примитивы.
mutex_enter () Захват блокировки; если уже захвачена — блокирование
потока
mutex_exit () Освобождение блокировки с возможным деблокированием ожидающего потока
mutex_tryenter () Определение состояния блокировки (захвачена ли она каким-либо потоком в настоящее время)
5 От mutex — mutualexclusion. — Прим. перев.
|
Примитив mutex_tryenter () обеспечивает программисту возможность применять на уровне пользовательских потоков технологию пережидания занятости, что позволяет избежать блокирования всего процесса в целом из-за блокирования одного из потоков.
Семафоры
Solaris поддерживает классические семафоры-счетчики, предоставляя для работы с ними следующие примитивы:
sema_p () Уменьшает значение семафора (с возможным блокированием
потока)
sema_v () Увеличивает значение семафора (с возможным деблокирова-
нием ожидающего потока)
sema_tryp () Уменьшает значение семафора (если не требуется блокиро-
вание)
Примитив sema_tryp () обеспечивает программисту возможность использовать на уровне пользовательских потоков технологию пережидания занятости.
Блокировки читатели/писатель
Данная блокировка обеспечивает возможность одновременного доступа только для чтения к защищенному ею объекту нескольким потокам, а также обеспечивает исключительный доступ для записи объекта одному потоку (исключительный означает, что когда такая блокировка оказывается захваченной потоком для записи, все остальные потоки, как читающие, так и записывающие, переходят в состояние ожидания). Для работы с блокировками этого вида используются следующие примитивы.
rw_enter () Попытка захвата блокировки для чтения или записи
rw_exit () Освобождение блокировки
rw_tryenter () Неблокирующий захват
rw_downgrade () Поток, захвативший блокировку для записи, превращает ее в блокировку для чтения. Все потоки записи в состоянии ожидания ждут освобождения блокировки. Если таковых нет, то примитив активизирует все потоки чтения rw_tryupgrade () Пытается преобразовать блокировку для чтения в блокировку для записи
Переменные условий
Данные переменные используются для ожидания выполнения некоторого условия и должны применяться вместе с блокировками взаимоисключений (тем самым реализуются мониторы, использованные в листинге 5.15). Для работы с ними имеются следующие примитивы.
Блокирование потока до тех пор, пока условие не будет выполнено
Активизация одного из потоков, заблокированных примитивом cv_wait()
Активизация всех потоков, заблокированных примитивом cv wait()
Примитив cv_wait() освобождает связанный с ним мьютекс перед блокированием потока и вновь захватывает его перед завершением работы. Поскольку повторный захват мьютекса может быть заблокирован другим ожидающим потоком, следует повторно проверить выполнение условия, вызвавшего ожидание. Таким образом, типичный фрагмент кода, работающего с переменными условий, • выглядит следующим образом:
Поскольку при таком подходе условие защищено мьютексом, в качестве него может использоваться сложное выражение.