Почему в C++0x или Boost.Thread нет блокировок с ограниченной областью действия для нескольких мьютексов?

Библиотека потоков С++ 0x или Boost.thread определяют функцию шаблона с переменным числом переменных, не являющуюся членом, которая блокирует все блокировки, избегая тупиковой блокировки.

template <class L1, class L2, class... L3> void lock(L1&, L2&, L3&...);

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

{
  std::lock(l1,l2);
  // do some thing
  // unlock li l2 exception safe
}

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

{
  std::lock(l1,l2);
  std::unique_lock lk1(l1, std::adopted);
  std::unique_lock lk2(l2, std::adopted);
  // do some thing
  // unlock li l2 on destruction of lk1 lk2
}

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

{
  std::array_unique_lock<std::mutex> lk(l1,l2);
  // do some thing
  // unlock l1 l2 on destruction of lk
}

или кортежи мьютексов

{
  std::tuple_unique_lock<std::mutex, std::recursive_mutex> lk(l1,l2);
  // do some thing
  // unlock l1 l2 on destruction of lk
}

Что-то не так с дизайном?


Обновлено: описание из стандарта

template <class L1, class L2, class... L3> void lock(L1&, L2&, L3&...);

Требования: каждый тип параметра шаблона должен соответствовать требованиям Mutex, за исключением того, что вызов try_lock() может вызвать исключение. [Примечание. Шаблон класса unique_lock соответствует этим требованиям при правильном создании экземпляра. -конец примечания]

Эффекты: все аргументы блокируются с помощью последовательности вызовов lock(), try_lock() или unlock() для каждого аргумента. Последовательность вызовов не должна приводить к взаимоблокировке, но в остальном она не определена. [Примечание. Должен использоваться алгоритм предотвращения взаимоблокировок, такой как попытка и откат, но конкретный алгоритм не указан, чтобы избежать чрезмерного ограничения реализации. — конец примечания ] Если вызов lock() или try_lock() вызывает исключение, unlock() должен вызываться для любого аргумента, который был заблокирован вызовом lock() или try_lock().


Я принял ответ. Я понимаю, что основная причина в том, что нет достаточно времени, чтобы сделать библиотеку C++0x Thread лучше. Я надеюсь, что TR2 будет включать в себя гораздо больше вещей.


person Vicente Botet Escriba    schedule 01.05.2010    source источник
comment
Хороший вопрос, я не понимаю, почему нет scoped_multi_lock(T...).   -  person Georg Fritzsche    schedule 02.05.2010
comment
Действительно, мне кажется, что lock(...) функции несбалансированы. По крайней мере, я бы также ожидал эквивалентного unlock(...). Однако кажется, что лучше всего добавить такую ​​функциональность в класс lock_guard. При этом его можно было построить с несколькими запирающимися предметами и разблокировать их при разрушении. В отличие от множества других шаблонных классов.   -  person GrafikRobot    schedule 02.05.2010
comment
@График Ты прав. анлок тоже должен быть по стандарту. Разница в том, что unlock(...) не может зайти в тупик.   -  person Vicente Botet Escriba    schedule 02.05.2010
comment
@Grafik Что касается того, лучше ли сделать текущие вариативные шаблоны lock_guard и unique_lock или добавить новые вариативные классы, я думаю, что лучше разделить их, так как в большинстве случаев нам нужно было блокировать мьютекс за раз. Версия с переменным числом аргументов, несомненно, внесет больше сложностей, чем решение для общего случая одного единственного мьютекса.   -  person Vicente Botet Escriba    schedule 02.05.2010


Ответы (2)


Я думаю, что, предоставляя defer_lock_t (и accept_lock_t), ожидается, что использование будет похоже на ваш второй пример или, возможно, больше похоже на:

 std::unqiue_lock ul1(l1, std::deferred);
 std::unique_lock ul2(l2, std::deferred);
 std::lock(ul1, ul2);

Это безопасно для исключений и все такое хорошее.

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

person Logan Capaldo    schedule 01.05.2010
comment
export - это совершенно другая категория и (должны были бы) потребоваться серьезные архитектурные изменения в компиляторах. Как вы думаете, зачем ему столько глазури? Это должно быть просто однократная запись с точки зрения уже заданных примитивов. - person Georg Fritzsche; 02.05.2010
comment
@Logan Извините, я сделал ошибку, чтобы использовать accept вместо accept_lock. - person Vicente Botet Escriba; 02.05.2010
comment
@gf, это моя точка зрения. Он записывается один раз в терминах уже заданных примитивов, что делает его привлекательным, поэтому он не должен быть в стандарте или обременять разработчиков. - person Logan Capaldo; 02.05.2010
comment
@Logan Я понимаю все ваши объяснения, по крайней мере, для std, меньше для Boost, который предназначен для предложения новых инструментов для экспериментов. - person Vicente Botet Escriba; 02.05.2010
comment
экспорта, кстати, может быть плохим примером, но я просто хотел подчеркнуть мысль о том, что каким бы простым, легким или очевидным ни было осуществление чего-либо в принципе, это не делает его бесплатным, работа все равно должна происходить в упражняться. - person Logan Capaldo; 02.05.2010
comment
@Logan Я не вижу никакой разницы между кодом, который вы предлагаете, и тем, который я представил. Либо вы блокируете все и принимаете, либо откладываете и блокируете все. То же самое, не так ли? - person Vicente Botet Escriba; 02.05.2010
comment
@Logan Просто из любопытства. Если бы вариационная уникальная блокировка была стандартом, вы бы ее использовали? - person Vicente Botet Escriba; 02.05.2010
comment
@Vicente Я думаю, что сначала установить защиту блокировки немного безопаснее, потому что вы случайно не добавите блокировку к вызову блокировки и не забудете добавить усыновление, поскольку охранники идут первыми. По крайней мере, я чувствую, что в любом случае будет меньше шансов ошибиться с этим заказом. Что касается использования шаблона вариативной блокировки, если бы он существовал, если бы мне нужно было заблокировать несколько таких блокировок, я бы, вероятно, так и сделал. Хотя я стараюсь не ставить себя в такое положение. - person Logan Capaldo; 02.05.2010
comment
Я мог бы согласиться, но пользователь может забыть также отложить блокировку при установке защиты блокировки. Понятно, что функция решит любые вопросы. Мне нужно было заблокировать несколько мьютексов за раз при реализации TBoost.STM библиотеки программной транзакционной памяти, которая скрывает от пользователя весь механизм синхронизации, поэтому необходимо несколько раз блокировать несколько мьютексов. Я не могу сказать, требуется ли это обычно. - person Vicente Botet Escriba; 02.05.2010

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

person Puppy    schedule 01.05.2010
comment
Пожалуйста, ознакомьтесь со стандартом. Он использует алгоритм, использующий try_lock и unlock, которые помогают избежать взаимоблокировки, в случаях, когда два потока берут оба журнала, используя одну и ту же функцию. - person Vicente Botet Escriba; 02.05.2010
comment
Чем это отличается от того, что ОП просто блокирует их обоих сам? Возможно, я неправильно понял вопрос, но я подумал, что он предполагает, что, имея такую ​​​​структуру с несколькими блокировками, вы, очевидно, мгновенно избежите взаимоблокировок, а не блокируете их одну за другой. - person Puppy; 02.05.2010
comment
Нет, речь шла о безопасности исключений и мультиблокировках в стиле RAII. Прочтите документацию Boost.Thread или проект C++0x о lock(). - person Georg Fritzsche; 02.05.2010
comment
пожалуйста, прочитайте эффект блокировки (...), который я добавил к вопросу - person Vicente Botet Escriba; 04.05.2010