Локаем много мьютексов
#опытным
Cтандартное решение этой проблемы дедлока из постов выше - лочить замки в одном и том же порядке во всех потоках. Но как это сделать? Они не же на физре, "по порядку рассчитайсьььь" не делали.
Можно конечно на ифах городить свой порядок на основе, например, адресов мьютексов. Но это какие-то костыли и так делать не надо.
Так как проблема довольно стандартная, то и решение мы скорее всего найдем в том же стандарте.
std::scoped_lock был введен в С++17 и представляет собой RAII обертку над локом множества мьютексов. Можно сказать, что это std::lock_guard на максималках. То есть буквально, это обертка, которая лочит любое количество мьютексов от 0 до "сами проверьте верхнюю границу".
Но есть один важный нюанс.
Никак не гарантируется порядок, в котором будут блокироваться замки. Гарантируется лишь то, что выбранный порядок не будет приводить к dead-lock'у.
Пример из прошлого поста может выглядеть теперь вот так:
struct SomeSharedResource {
void swap(SomeSharedResource& obj) {
{
std::scoped_lock lg{mtx, obj.mtx};
// handle swap
}
}
std::mutex mtx;
};
int main() {
SomeSharedResource resource1;
SomeSharedResource resource2;
std::mutex m2;
std::thread t1([&resource1, &resource2] {
resource1.swap(resource2);
std::cout << "1 Do some work" << std::endl;
});
std::thread t2([&resource1, &resource2] {
resource2.swap(resource1);
std::cout << "2 Do some work" << std::endl;
});
t1.join();
t2.join();
}
И все. И никакого дедлока.
Однако немногое лишь знают, что std::scoped_lock - это не только RAII-обертка. Это еще и более удобная обертка над "старой" функцией из С++11 std::lock.
О ней мы поговорим в следующий раз. Ведь не всем доступны самые современные стандарты. Да и легаси код всегда есть.
Be comfortable to work with. Stay cool.
#cpp17 #cpp11 #concurrency