Русская Википедия:Мьютекс

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Шаблон:Falseredirect

Файл:Mutual exclusion example with linked list.png
Попытка одновременного удаления двух узлов i и i + 1 приводит к сохранению узла i + 1.

Мью́текс (Шаблон:Lang-en, от Шаблон:Lang-en2 — «взаимное исключение») — примитив синхронизации, обеспечивающий взаимное исключение исполнения критических участков кодаШаблон:Sfn. Классический мьютекс отличается от двоичного семафора наличием эксклюзивного владельца, который и должен его освобождать (т.е. переводить в незаблокированное состояние)[1]. От спинлока мьютекс отличается передачей управления планировщику для переключения потоков при невозможности захвата мьютекса[2]. Встречаются также блокировки чтения-записи, именуемые разделяемыми мьютексами и предоставляющие помимо эксклюзивной блокировки общую, позволяющую совместно владеть мьютексом, если нет эксклюзивного владельцаШаблон:Sfn.

Условно классический мьютекс можно представить в виде переменной, которая может находиться в двух состояниях: в заблокированном и в незаблокированном. При входе в свою критическую секцию поток вызывает функцию перевода мьютекса в заблокированное состояние, при этом поток блокируется до освобождения мьютекса, если другой поток уже владеет им. При выходе из критической секции поток вызывает функцию перевода мьютекса в незаблокированное состояние. В случае наличия нескольких заблокированных по мьютексу потоков во время разблокировки планировщик выбирает поток для возобновления выполнения (в зависимости от реализации это может быть, как случайный, так и детерминированный по некоторым критериям поток)Шаблон:Sfn.

Задачей мьютекса является защита объекта от доступа к нему других потоков, отличных от того, который завладел мьютексом. В каждый конкретный момент только один поток может владеть объектом, защищённым мьютексом. Если другому потоку будет нужен доступ к данным, защищённым мьютексом, то этот поток блокируется до тех пор, пока мьютекс не будет освобождён. Мьютекс защищает данные от повреждения в результате асинхронных изменений (состояние гонки), однако при неправильном использовании могут порождаться другие проблемы, например, взаимная блокировка или двойной захват.

По типу реализации мьютекс может быть быстрым, Шаблон:Нп3 или с контролем ошибок.

Проблемы использования

Инверсия приоритетов

Шаблон:См. также Инверсия приоритетов возникает, когда должен исполняться процесс с высоким приоритетом, но он блокируется по мьютексу, которым владеет процесс с низким приоритетом, и должен ожидать, пока процесс с низким приоритетом не разблокирует мьютекс. Классическим примером неограниченной инверсии приоритетов в системах реального времени является захват процессорного времени процессом со средним приоритетом, в результате чего процесс с низким приоритетом не может исполняться и не может разблокировать мьютекс[3].

Типовым решением проблемы является наследование приоритетов, при котором процесс, владеющий мьютексом, наследует приоритет другого процесса, заблокированного по нему, если приоритет заблокированного процесса выше, чем у текущего[3].

Прикладное программирование

Мьютексы в Win32 API

Win32 API в Windows имеет две реализации мьютексов — собственно мьютексы, имеющие имена и доступные для использования между разными процессами[4], и критические секции, которые могут использоваться только в пределах одного процесса разными потоками[5]. Для каждого из этих двух типов мьютексов используются свои функции захвата и освобождения[6]. Критическая секция в Windows работает несколько быстрее и является более эффективной по сравнению с мьютексом и семафором, поскольку использует специфичную для процессора инструкцию test-and-set[5].

Мьютексы в POSIX

Пакет Pthreads предоставляет различные функции, которые можно использовать для синхронизации потоковШаблон:Sfn. Среди этих функций есть и функции для работы с мьютексами. В дополнение к функциям захвата и освобождения мьютекса предусмотрена функция попытки захвата мьютекса, которая возвращает ошибку, если предвидится блокировка потока. Данную функцию можно использовать в активном цикле ожидания, если возникает такая необходимостьШаблон:Sfn.

Функции пакета Pthreads для работы с мьютексами
Функция Описание
pthread_mutex_init() Создание мьютексаШаблон:Sfn.
pthread_mutex_destroy() Уничтожение мьютексаШаблон:Sfn.
pthread_mutex_lock() Перевод мьютекса в заблокированное состояние (захват мьютекса)Шаблон:Sfn.
pthread_mutex_trylock() Попытка перевода мьютекса в заблокированное состояние, и возврат ошибки в случае, если должна произойти блокировка потока из-за того, что у мьютекса уже есть владелецШаблон:Sfn.
pthread_mutex_timedlock() Попытка перевода мьютекса в заблокированное состояние, и возврат ошибки в случае, если попытка не удалась до наступления указанного момента времени[7].
pthread_mutex_unlock() Перевод мьютекса в незаблокированное состояние (отпускание мьютекса)Шаблон:Sfn.

Для решения специализированных задач мьютексам могут задаваться различные атрибутыШаблон:Sfn. Через атрибуты с помощью функции pthread_mutexattr_settype() можно задать тип мьютекса, что повлияет на поведение функций захвата и освобождения мьютекса[8]. Мьютекс может быть одного из трёх типов[8]:

  • PTHREAD_MUTEX_NORMAL — при повтроной попытке захвата мьютекса владеющим потоком происходит взаимоблокировка[9];
  • PTHREAD_MUTEX_RECURSIVE — повторные захваты тем же потоком допустимы, ведётся подсчёт таких захватов[9];
  • PTHREAD_MUTEX_ERRORCHECK — попытка повторного захвата тем же потоком возвращает ошибку[9].

Мьютексы в языке Си

Стандарт С17 языка программирования Си определяет тип mtx_tШаблон:Sfn и набор функций для работы с нимШаблон:Sfn, которые должны быть доступны, если макрос __STDC_NO_THREADS__ не был определён компиляторомШаблон:Sfn. Семантика и свойства мьютексов в целом совпадают со стандартом POSIX.

Тип мьютекса определяется передачей комбинации флагов в функцию mtx_init()Шаблон:Sfn:

  • mtx_plain — нет контроля повторного захвата тем же потокомШаблон:Sfn;
  • mtx_recursive — повторные захваты тем же потоком допустимы, ведётся счётчик таких захватовШаблон:Sfn;
  • mtx_timed — поддерживается захват мьютекса с возвращением ошибки по истечении указанного времениШаблон:Sfn.

Возможность использования мьютексов через разделяемую память различными процессами в стандарте C17 не рассматривается.

Мьютексы в языке C++

Стандарт C++17 языка программирования C++ определяет 6 различных классов мьютексовШаблон:Sfn:

  • mutex — мьютекс без контроля повторного захвата тем же потокомШаблон:Sfn;
  • recursive_mutex — повторные захваты тем же потоком допустимы, ведётся подсчёт таких захватовШаблон:Sfn;
  • timed_mutex — нет контроля повторного захвата тем же потоком, имеет дополнительные методы захвата мьютекса с возвратом значения false в случае истечения тайм-аута или по достижении указанного времениШаблон:Sfn;
  • recursive_timed_mutex — повторные захваты тем же потоком допустимы, ведётся подсчёт таких захватов, имеет дополнительные методы захвата мьютекса с возвратом кода ошибки по истечении тайм-аута или по достижении указанного времениШаблон:Sfn;
  • shared_mutex — разделяемый мьютексШаблон:Sfn;
  • shared_timed_mutex — разделяемый мьютекс, имеет дополнительные методы захвата мьютекса с возвратом кода ошибки по истечении тайм-аута или по достижении указанного времениШаблон:Sfn.

Библиотека Boost дополнительно обеспечивает именованные и межпроцессные мьютексы, а также разделяемые мьютексы, которые позволяют захватывать мьютекс для совместного владения несколькими потоками только для чтения данных с запретом на эксклюзивную запись на время захвата блокировки, что по сути представляет собой механизм блокировок чтения и записи[10].

Детали реализации

На уровне операционных систем

В общем случае мьютекс хранит в себе не только своё состояние, но и список заблокированных задач. Изменение состояния мьютекса может быть реализовано с помощью архитектурно-зависимых атомарных операций на уровне пользовательского кода, но по разблокированию мьютекса необходимо также возобновить исполнение других задач, которые были заблокированы по мьютексу. Для этих целей хорошо подходит более низкоуровневый примитив синхронизации — фьютекс, который реализуется на стороне операционной системы и берёт на себя функционал блокировки и разблокировки задач, позволяя в том числе создавать межпроцессовые мьютексы[11]. В частности, с помощью фьютекса мьютекс реализован в пакете Pthreads во многих дистрибутивах Linux[12].

В архитектурах x86 и x86_64

Простота мьютексов позволяет реализовать их в пространстве пользователя с помощью ассемблерной команды XCHG, которая может атомарно копировать значение мьютекса в регистр и одновременно выставлять значение мьютекса в 1 (предварительно записанное в тот же регистр). Нулевое значение мьютекса означает, что он находится в заблокированном состоянии, а единичное — в разблокированном. Значение из регистра может быть протестировано на 0, и в случае нулевого значения управление должно быть возвращено программе, что означает захват мьютекса, если же значение являлось ненулевым, то управление должно быть передано планировщику для возобновления работы другого потока с последующей повторной попыткой захвата мьютекса, что служит аналогом активной блокировки. Разблокировка мьютекса осуществляется сохранением в мьютексе значения 0 с помощью команды XCHGШаблон:Sfn. Альтернативно может использоваться LOCK BTS (реализация TSL для одного бита) или CMPXCHG[13] (реализация CAS).

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

В архитектуре ARM

Шаблон:См. также В архитектуре ARMv7 для синхронизации памяти между процессорами используются так называемые локальный и глобальный эксклюзивные мониторы, представляющие собой автоматы состояний, контролирующие атомарный доступ к ячейкам памятиШаблон:SfnШаблон:Sfn. Атомарное чтение ячейки памяти может осуществляться с помощью инструкции LDREXШаблон:Sfn, а атомарная запись — через инструкцию STREX, которая также возвращает флаг успеха операцииШаблон:Sfn.

Алгоритм захвата мьютекса предполагает чтение его значения с помощью LDREX и проверку прочитанного значения на заблокированное состояние, что соответствует значению 1 переменной мьютекса. В случае, если мьютекс заблокирован, вызывается код ожидания освобождения блокировки. Если же мьютекс был в незаблокированном состоянии, то попытка блокировки может быть осуществлена с помощью инструкции эксклюзивной записи STREXNE. Если запись не удалась из-за того, что значение мьютекса изменилось, то алгоритм захвата повторяется с началаШаблон:Sfn. После захвата мьютекса выполняется инструкция DMB, обеспечивающая целостность памяти защищаемого мьютексом ресурсаШаблон:Sfn.

Перед освобождением мьютекса также вызывается инструкция DMB, после чего в переменную мьютекса записывается значение 0 с помощью инструкции STR, что означает перевод в разблокированное состояние. После разблокировки мьютекса должно произойти сигнализирование ожидающим задачам, если таковые есть, о том, что мьютекс был освобождёнШаблон:Sfn.

См. также

Примечания

Шаблон:Примечания

Литература

Шаблон:IPC