Нерекурсивное владение мьютексом - PullRequest
3 голосов
/ 24 февраля 2010

Я прочитал этот ответ на SO:

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

Я смущен последним утверждением. Может ли один поток заблокировать мьютекс, а другой - разблокировать этот мьютекс? Я думал, что та же самая нить должна быть единственной, способной разблокировать мьютекс? Или есть какой-то конкретный мьютекс, который позволяет это? Я надеюсь, что кто-то может уточнить.

Ответы [ 3 ]

6 голосов
/ 24 февраля 2010

нерекурсивный мьютекс

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

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

Рекурсивный мьютекс

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

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

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


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

Thread 1

    Acquire mutex A
    // Modify or read shared data
    Release mutex A

Thread 2

    Attempt to acquire mutex A
    Block as thread 1 has mutex A
    When thread 1 has released mutex A, acquire it
    // Modify or read shared data
    Release mutex A

Он становится более сложным, когда у вас есть несколько мьютексов, которые могут быть получены одновременно (скажем, мьютекс A и B).Существует риск того, что вы попадете в тупиковую ситуацию, подобную этой:

Thread 1

    Acquire mutex A
    // Access some data...

*** Context switch to thread 2 ***

Thread 2

    Acquire mutex B
    // Access some data

*** Context switch to thread 1 ***

    Attempt to acquire mutex B
    Wait for thread 2 to release mutex B

*** Context switch to thread 2 ***

    Attempt to acquire mutex A
    Wait for thread 1 to release mutex A

*** DEADLOCK ***

Теперь у нас есть ситуация, когда каждый поток ожидает, пока другой поток освободит блокировку other - это называется шаблоном взаимоблокировки ABBA .

. Чтобы предотвратить эту ситуацию, важно, чтобы каждый поток всегда получал мьютексы в порядке (например,всегда A, затем B).

1 голос
/ 24 февраля 2010

Я думаю, что это охватывает все ваши вопросы. Прямо из man-страниц Linux для pthreads:

Если тип мьютекса - PTHREAD_MUTEX_NORMAL, обнаружение взаимоблокировки не должно обеспечиваться. Попытка разблокировать мьютекс вызывает тупик. Если нить попытка разблокировать мьютекс, который он не заблокировал, или мьютекс, который разблокирован, приводит к неопределенному поведению.

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

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

Если тип мьютекса - PTHREAD_MUTEX_DEFAULT, попытка рекурсивной блокировки мьютекса приводит к неопределенному поведению. Попытка разблокировать мьютекс если он не был заблокирован вызывающим потоком, это приводит к неопределенному поведению. Попытка разблокировать мьютекс, если он не заблокирован, приводит к неопределенному поведению. поведение.

1 голос
/ 24 февраля 2010

Рекурсивные мьютексы по своему дизайну зависят от потока (та же самая блокировка потока снова = рекурсия, другая блокировка потока между тем = блокировка). Обычные мьютексы не имеют такой конструкции, и поэтому они могут быть заблокированы и разблокированы в разных потоках.

...