View in Telegram
Почему не используют стратегию блокировки по адресам? #опытным Точного ответа от разработчиков стандартной библиотеки мы не услышим, но я приведу некоторые рассуждения, которые могут натолкнуть на некоторые мысли. Начнем с того, что локать один мьютекс - это норма. Все так делают, никто от этого не помер. Проблемы и эти ваши дедлоки начинаются только тогда, когда поток по какой-то причине блокируется с уже захваченным локом. А именно это и происходит при вызове метода lock(). Поток мытается захватить мьютекс и если не получается - блокируется до того момента, пока мьютекс не освободится. Поэтому любая схема с последовательным вызовом методов lock() будет подвержена дедлокам. А в схеме с упорядоченным по адресам блокировкой именно так и происходит. Да, эта схема безопасна, если все мьютексы будут захватываться только так. Но в реальных системах все намного сложнее. А что если в одном потоке замки будут лочиться через scoped_lock по адресной схеме, а в другом потоке - одиночно?
            1 поток            |            2 поток              |
-------------------------------|---------------------------------|
 lock(mutex2) // УСПЕШНО       |                                 |
                               | scoped_lock()                   |
                               |    lock(mutex1) // УСПЕШНО      |
                               |    lock(mutex2) // ОЖИДАНИЕ ... |
 lock(mutex1) // ОЖИДАНИЕ...   |                                 |
В этом случае настанет дедлок. Спасибо за пример Сергею Борисову. Ну или другую ситуацию рассмотрим: есть 4 замка l1, l2, l3, l4. Поток захватил замок с самым большим адресом l4 и надолго(потенциально навсегда) заблокировался. Но другие треды продолжают нормально работать. И они иногда захватывают пары мьютексов. Все продолжается нормально, пока один из потоков не пытается залочить l3 и l4. Из-за ордеринга захватится l3, а дальше поток будет ждать освобождения l4 aka заблокируется. Дальше другой поток будет пытаться захватить l2 и l3. Он захватит l2 и будет дожидаться l3. Логику можно продолжать и дальше. Таким образом из-за одного мьютекса и немного поломанного потока может остановиться вся программа.
std::mutex l1, l2, l3, l4;
// Пусть я как-то гарантирую, что они пронумерованы в порядке
 возрастания адресов и std::scoped_lock(sc_lock для краткости) 
работает с помощью сортировки по адресам

1 поток      |     2 поток    |     3 поток    |       4 поток       
-------------|----------------|----------------|----------------
 l4.lock();  |                |                |
//blocks here|                |                |
             |sc_lock(l3, l4);|                |
             | // lock l3     |                |
             | // blocks on l4|                |
                              |sc_lock(l2, l3);|
                              | // lock l2     |
                              | // blocks on l3|
                              |                | sc_lock(l1, l2);
                                               | // lock l1      
                                               | // blocks on l2
Примеры немного преувеличены, но тем не менее они говорят о том, что схема с адресами не совсем безопасна. Так может тогда вообще не будем блокироваться при уже захваченном мьютексе? Именно это и делают в реализации стандартной библиотеки. Первый захват мьютекса происходит через обычный lock(), а остальные мьютексы пытаются заблокировать через try_lock. Можно сказать, что это lock-free взятие замка. Если мьютекс можно захватить - захватываем, если нет, то не блокируемся и дальше продолжаем исполнение. Так вот в случае, если хотя бы один try_lock для оставшихся замков вернул false, то реализация освобождает все захваченные замки и начинает попытку снова. Такой алгоритм позволит избежать неприятных последствий обеих ситуаций, представленных выше. ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ #concurrency
Love Center
Love Center
Find friends or serious relationships easily