Есть ли идиоматическое явное использование mutex::lock() или unlock()?

Рекомендуемый способ использования mutex для блокировки критической области кода — через RAII, т.е.

mutex_type mutex;
{ // start of critical region
  std::lock_guard<mutex_type> lock(mutex);   // first statement in critical region
  // ... do critical stuff, may throw an exception
} // end of critical region

так что, когда в критической области возникает исключение, мьютекс все равно будет разблокирован (деструктором std::lock_guard). Однако таким образом члены mutex::lock() и mutex::unlock() никогда не вызываются явным образом кодом пользователя.

Вопрос. Что является основным идиоматическим явным использованием mutex::lock()?

Я спрашиваю, иначе нет смысла иметь mutex::lock() публичного члена, продвигающего плохой код (тот, который избегает std::lock_guard).

Изменить Поскольку и std::lock_guard<>, и std::mutex определены в одном и том же заголовке, std::mutex может легко подружиться с std::lock_guard<std::mutex и защитить свои методы lock() и unlock():

class mutex      // use only with lock_guard<mutex>
{
  friend class lock_guard<mutex>;         // support acquire-release semantic via RAII
  friend class scoped_lock_guard<mutex>;  // for supporting more complicated semantic,
                                          //     possibly remembering the mutex state.
  // ...
protected:
  void lock();
  bool try_lock();
  void unlock();       
};

class raw_mutex  // use if you absolutely must explicitly lock, try_lock, or unlock
: public mutex
{
public:
  using mutex::lock;
  using mutex::try_lock;
  using mutex::unlock;
};

Аргумент в отношении ответа на мой вопрос заключается в том, что единственный безопасный способ использования mutex::lock() — это использование RAII. Таким образом, единственно разумное явное использование должно включать только noexcept методов между вызовами lock (или try_lock) и unlock. Однако, поскольку noexcept является только наводящим на размышления и ничего не обещает, такое использование было бы небезопасным. Вопрос правильно?


person Walter    schedule 04.04.2014    source источник
comment
codereview.stackexchange.com/q/9431/2503 thread_start_lock   -  person Lightness Races in Orbit    schedule 06.04.2014
comment
@LightnessRacesinOrbit ваш код выглядит довольно пугающе. Совсем не очевидно, что thread_start_lock.unlock() всегда вызывается тем же потоком, что и thread_start_lock.lock(), на самом деле все выглядит наоборот.   -  person Walter    schedule 08.04.2014
comment
Да, в этом суть.   -  person Lightness Races in Orbit    schedule 08.04.2014
comment
@LightnessRacesinOrbit ??? Итак, дело в том, что ваш код вызывает UB? Вы должны хотя бы добавить утверждение, что это не так.   -  person Walter    schedule 09.04.2014
comment
При чем здесь УБ?   -  person Lightness Races in Orbit    schedule 09.04.2014
comment
@LightnessRacesinOrbit случай UB: вызов unlock() в разблокированном мьютексе или в другом потоке, чем тот, который вызвал lock(). Неясно, избегает ли ваш код их.   -  person Walter    schedule 10.04.2014
comment
Нет, он определенно не избегает этого: он делает это намеренно. Как я уже сказал, в этом вся суть (читайте комментарии в коде). Я не знал, что это UB, но черт. Если у вас есть минутка, возможно, вы могли бы определить это как проблему с кодом в вопросе CodeReview.SE и предложить альтернативный способ сделать то, что я делаю.   -  person Lightness Races in Orbit    schedule 10.04.2014
comment
Один случай, который я видел (мне придется раскопать, где он находится, но я думаю, что он где-то в libstdc++), заключается в том, что кто-то создал класс RAII, который вызывает unlock() при построении и lock() при разрушении. Странный случай, но все же. Делать это очень сложным кажется плохой идеей.   -  person Dave S    schedule 09.01.2016
comment
@DaveS такой код не будет безопасным для исключений, поскольку он оставляет мьютекс в заблокированном состоянии после разрушения (вызывается при раскрутке стека после создания исключения). Это имеет смысл только в том случае, если есть другой охранник (во внешней области видимости), чей деструктор unlock()s. Однако такую ​​двойную защиту можно было бы более четко реализовать в одном объекте, таком как scoped_lock_guard.   -  person Walter    schedule 10.01.2016


Ответы (2)


lock_guard — не единственное, что должно вызывать lock/unlock на mutex. unique_lock, lock, try_lock и condition_variable_any также должны работать с мьютексами. И это только стандартные типы. Дружба в этом случае создает тесную связь, которая становится помехой.

person Nevin    schedule 06.04.2014

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

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

То же самое может произойти, когда мы используем mutexes для взаимного исключения. Рассмотрим эту задачу:

Ваша программа управляет двумя ресурсами, она должна прочитать и выполнить последовательность команд. Возможные команды:

  • заблокировать ресурс №1: 1>
  • заблокировать ресурс #2: 2>
  • разблокировать ресурс №1: 1<
  • разблокировать ресурс #2 : 2<
  • напишите x в ресурс №1: 1x
  • напишите x в ресурс #2 : 2x

ваша программа может ожидать такие входные данные, как 1> 1a 2> 2b 1c 1< 2d 2<, что делает невозможным использование статических областей для RAII, если программа подчиняется командам (области должны частично перекрываться). Так что я думаю, что это иллюстрирует одну ситуацию, когда необходима явная блокировка/разблокировка.


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

Рассмотрим два параллельных процесса, P и Q, каждый из которых имеет определенную точку в своем коде. Мы требуем, чтобы Q не мог пройти назначенную ему точку, пока P не достигнет своей. Это требование можно выполнить, используя mutex, инициализированное состоянием locked, и поместив операцию lock() прямо перед назначенной точкой Q и unlock() сразу после назначенной точки P. Можно легко убедиться, что эта установка решает проблему.

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

person xxa    schedule 14.04.2014