Чому я не можу перевірити, чи не заблоковано мютекс?


28

C ++ 14, здається, опустив механізм перевірки std::mutex, заблоковано чи ні. Дивіться це питання ТАК:

/programming/21892934/how-to-assert-if-a-stdmutex-is-locked

Існує кілька способів цього, наприклад, використовуючи;

std::mutex::try_lock()
std::unique_lock::owns_lock()

Але жодне з них не є особливо задовольняючими рішеннями.

try_lock()дозволено повертати помилковий негатив і має невизначене поведінку, якщо поточна нитка заблокувала мютекс. Він також має побічні ефекти. owns_lock()вимагає побудови unique_lockверху над оригіналом std::mutex.

Очевидно, я міг би розгорнути свій власний, але я краще зрозумів мотивацію поточного інтерфейсу.

Можливість перевірити стан мютексу (наприклад std::mutex::is_locked()) не здається мені езотеричним запитом, тому я підозрюю, що Комітет зі стандартів навмисно опустив цю функцію, а не як нагляд.

Чому?

Редагувати: Гаразд, можливо, цей випадок використання не такий поширений, як я очікував, тому я проілюструю свій конкретний сценарій. У мене є алгоритм машинного навчання, який розподіляється на декілька потоків. Кожен потік функціонує асинхронно і повертається в основний пул, як тільки він закінчив проблему оптимізації.

Потім він блокує основний мютекс. Потім потік повинен вибрати нового батьків, з якого мутуватиме потомство, але може вибирати лише батьків, у яких наразі немає потомства, оптимізованих іншими потоками. Тому мені потрібно здійснити пошук, щоб знайти батьків, які наразі не заблоковані іншою темою. Не існує ризику зміни статусу мютексу під час пошуку, оскільки мутекс головного потоку заблокований. Очевидно, є й інші рішення (я зараз використовую булевий прапор), але я подумав, що mutex пропонує логічне рішення цієї проблеми, оскільки вона існує для межпотокової синхронізації.


42
Ви дійсно не можете перевірити, чи не заблоковано мютекс, оскільки одна наносекунда після перевірки може розблокуватися чи заблокуватися. Отже, якщо ви написали "if (mutex_is_locked ()) ...", тоді mutex_is_locked може повернути правильний результат, але до моменту виконання "if" це неправильно.
gnasher729

1
Це ^. Яку корисну інформацію ви сподіваєтеся отримати is_locked?
Марно

3
Це відчувається як проблема XY. Чому ви намагаєтеся запобігти повторному використанню батьків лише під час народження дитини? Чи є у вас вимога, щоб будь-який з батьків мав лише одне потомство? Ваш замок цього не завадить. У вас немає чітких поколінь? Якщо ні, чи знаєте ви, що особи, які можна оптимізувати швидше, мають більш високу придатність, оскільки їх можна обирати частіше / раніше? Якщо ви все-таки використовуєте покоління, то чому б ви не вибрали всіх батьків наперед, тоді дозвольте потокам вивести батьків із черги? Чи справді генерувати потомство так дорого, що вам потрібно кілька ниток?
амон

10
@quant - я не бачу, чому ваші батьківські об’єкти мутекси у вашому прикладі програми повинні взагалі бути мютексами: якщо у вас є головний mutex, який заблокований, коли вони встановлені, ви можете просто використовувати булеву змінну, щоб вказати їх статус.
Periata Breatta

4
Я не згоден з останнім реченням питання. Просте булеве значення тут набагато чистіше, ніж мютекс. Зробіть це атомним булом, якщо ви не хочете заблокувати головну файлу для "повернення" батьків.
Себастьян Редл

Відповіді:


53

Я бачу принаймні дві серйозні проблеми із запропонованою операцією.

Перший уже згадувався в коментарі @ gnasher729 :

Ви дійсно не можете перевірити, чи не заблоковано мютекс, оскільки одна наносекунда після перевірки може розблокуватися чи заблокуватися. Тож якщо ви писали if (mutex_is_locked ()) …тоді, ви mutex_is_lockedможете повернути правильний результат, але до моменту його ifвиконання - це неправильно.

Єдиний спосіб бути впевненим у тому, що властивість мьютексу "заблоковано" не змінюється - це заблокувати його самостійно.

Друга проблема, яку я бачу, полягає в тому, що, якщо ви не заблокуєте mutex, ваша нитка не синхронізується з потоком, який раніше був заблокований мютекс. Тому говорити про "до" і "після" не можна навіть про те, чи є мютекс заблокованим чи ні, це начебто запитати, чи живе кот Шредігера в даний час, не намагаючись відкрити скриньку.

Якщо я правильно розумію, то обидві проблеми будуть суперечливими у вашому конкретному випадку завдяки заблокованому майстер-файлу. Але це не здається мені особливо поширеним випадком, тому я вважаю, що комітет зробив правильно, не додавши функції, яка може бути дещо корисною в дуже спеціальних сценаріях і завдати шкоди у всіх інших. (В дусі: "Зробіть інтерфейси простими у використанні та їх складне неправильне використання.")

І якщо я можу сказати, я думаю, що налаштування, яке ви зараз маєте, не є найелегантнішим, і його можна буде відновити, щоб уникнути проблеми взагалі. Наприклад, замість того, щоб головна нитка перевіряла всіх потенційних батьків на того, хто наразі не заблокований, чому б не підтримувати чергу готових батьків? Якщо нитка хоче оптимізувати іншу, вона вискакує наступну з черги і, як тільки у неї з’являються нові батьки, додає їх у чергу. Таким чином, вам навіть не потрібна головна нитка як координатор.


Дякую, це хороша відповідь. Причина, по якій я не хочу підтримувати чергу з готовими батьками, полягає в тому, що мені потрібно зберегти порядок, в якому були створені батьки (оскільки це диктує їхню тривалість життя). Це легко зробити за допомогою черги LIFO. Якщо я почав бити речі всередину, мені доведеться підтримувати окремий механізм впорядкування, який би ускладнював речі, отже, і теперішній підхід.
кількісне

14
@quant: Якщо у вас є дві цілі встановити чергу з батьками, ви можете зробити це з двома чергами ....

@quant: Ви видаляєте елемент (щонайбільше) один раз, але, імовірно, обробляєте кожен раз, тому ви оптимізуєте рідкісний випадок за рахунок загальної справи. Це рідко бажано.
Джеррі Труну

2
Але це розумно запитати , чи є поточний потік заблокований м'ютекс.
Обмежене спокутування

@LimitedAtonement Не дуже. Для цього мутекс повинен зберігати додаткову інформацію (ідентифікатор потоку), роблячи це повільніше. Рекурсивні мутекси вже роблять це, замість них.
StaceyGirl

9

Здається, ви використовуєте вторинні файли не для блокування доступу до проблеми оптимізації, а для визначення того, чи оптимізується проблема оптимізації зараз чи ні.

Це зовсім непотрібно. У мене був би список проблем, які потребують оптимізації, список проблем, що оптимізуються зараз, і список проблем, які були оптимізовані. (Не сприймайте "список" буквально, але це означає "будь-яку відповідну структуру даних".

Операції додавання нової проблеми до списку неоптимізованих проблем або переміщення проблеми з одного списку в інший здійснюватимуться під захистом єдиного «головного» мютексу.


1
Ви не вважаєте, що об’єкт типу std::mutexпідходить для такої структури даних?
кількісне

2
@quant - ні. std::mutexпокладається на певну операційну систему, встановлену mutex, яка може забирати ресурси (наприклад, ручки), які обмежені та повільно виділяють та / або працюють. Використання єдиного файлу mutex для блокування доступу до внутрішньої структури даних, ймовірно, буде набагато ефективнішим, а можливо і більш масштабованим.
Periata Breatta

1
Також врахуйте змінні умови. Вони можуть зробити безліч структур даних, подібних до цього, дуже просто.
Корт Аммон - Відновіть Моніку

2

Як говорили інші, немає жодного випадку використання, коли is_lockedна mutex не приносять користі, тому функція не існує.

Випадок у вас виникли проблеми з неймовірно поширеним, це в основному те , що робочі потоки роблять, які є одним з, якщо не в найбільш загальній реалізації потоків.

У вас поличка з 10 ящиками на ній. У вас є 4 працівники, які працюють з цими скриньками. Як ви переконаєтесь, що 4 працівники працюють на різних ящиках? Перший працівник знімає коробку з полиці, перш ніж вони починають працювати на ній. Другий робітник бачить 9 полиць на полиці.

Немає мітексів для фіксації ящиків, тому бачити стан уявного мютексу на коробці не обов’язково, а зловживати мютексом як булевим - просто неправильно. Мутекс замикає полицю.


1

На додаток до двох причин, наведених у відповіді 5gon12eder вище, я хочу додати, що це не є ні необхідним, ні бажаним.

Якщо ви вже тримаєте мютекс, то вам краще знати, що ви його тримаєте! Вам не потрібно питати. Як і у випадку володіння блоком пам’яті чи будь-яким іншим ресурсом, ви повинні точно знати, чи є у вас власник, чи коли це доречно випустити / видалити ресурс.
Якщо це не так, ваша програма розроблена погано, і ви прямуєте до неприємностей.

Якщо вам потрібно отримати доступ до спільного ресурсу, захищеного mutex, і ви вже не володієте mutex, тоді вам потрібно придбати mutex. Іншого варіанту немає, інакше логіка програми не правильна.
Ви можете виявити блокування прийнятним або неприйнятним у будь-якому випадку, lock()або try_lock()надасте потрібну вам поведінку. Все, що вам потрібно знати, позитивно і без сумніву, це те, чи успішно ви придбали мютекс (повернене значення try_lockговорить вам). Незалежно від того, чи хтось дотримується цього, чи ви отримали помилковий збій.

У будь-якому іншому випадку, прямо, це не ваша справа. Вам не потрібно знати, і ви не повинні знати, або робити припущення (щодо питань своєчасності та синхронізації, згаданих в іншому питанні).


1
Що робити, якщо я хочу виконати операцію ранжирування на ресурсах, які зараз доступні для блокування?
кількісне

Але це щось реалістичне? Я вважаю це досить незвичним. Я б сказав, що або в ресурсах вже є якийсь внутрішній вид ранжування, тоді вам потрібно буде зробити (придбати блокування для) найважливіше спочатку. Приклад: Потрібно оновити фізичне моделювання перед візуалізацією. Або ранжування більш-менш навмисне, тоді ви можете також try_lockперший ресурс, а якщо це не вдасться, спробуйте другий. Приклад: три стійких об'єднаних з'єднання з сервером бази даних, і вам потрібно використовувати одне для відправки команди.
Деймон

4
@quant - "операція ранжирування ресурсів, які наразі доступні для блокування" - загалом, робити подібні речі - це дійсно простий і швидкий спосіб написання коду, який заходить у тупикові місця, таким чином, як ви намагаєтеся з'ясувати. Виготовлення придбання замків і випуск детермінованого є майже у всіх випадках, кращої політика. Пошук блокування на основі критерію, який міг би змінитися, задає проблеми.
Periata Breatta

@PeriataBreatta Моя програма навмисно невизначена. Зараз я бачу, що цей атрибут не є загальним, тому я можу зрозуміти пропущення таких функцій, is_locked()які можуть полегшити таку поведінку.
Quant

@quant ранжування та блокування - цілком окремі проблеми. Якщо ви хочете якось сортувати чи переупорядкувати чергу із замком, заблокуйте її, відсортуйте її та розблокуйте її. Якщо вам потрібно is_locked, то існує набагато краще рішення вашої проблеми, ніж те, що ви маєте на увазі.
Пітер - Унбан Роберт Харві

1

Можливо, ви хочете використовувати atomic_flag з порядком пам'яті за замовчуванням. У ньому немає перегонів даних і ніколи не викидає винятки, як mutex робить при декількох викликах розблокування (і перериває безконтрольно, я можу додати ...). Крім того, існує атомна (наприклад, атомна [bool] або атомна [int] (з дужками трикутника, а не [])), яка має приємні функції, такі як load and Compare_exchange_strong.


1

Я хочу додати для цього випадок використання: Це дозволило б внутрішній функції забезпечити як передумову / твердження, що абонент дійсно тримає замок.

Для класів з декількома такими внутрішніми функціями та, можливо, з багатьма публічними функціями, що викликають їх, це може гарантувати, що хтось, додаючи іншу публічну функцію, що викликає внутрішню, дійсно придбав замок.

class SynchronizedClass
{

public:

void publicFunc()
{
  std::lock_guard<std::mutex>(_mutex);

  internalFuncA();
}

// A lot of code

void newPublicFunc()
{
  internalFuncA(); // whops, forgot to acquire the lock
}


private:

void internalFuncA()
{
  assert(_mutex.is_locked_by_this_thread());

  doStuffWithLockedResource();
}

};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.