Может ли scoped_lock заблокировать shared_mutex в режиме чтения?

C++17 представил оба std::shared_mutex а также std::scoped_lock, Моя проблема сейчас в том, что кажется, что scoped_lock блокирует общий мьютекс всегда в монопольном (записывающем) режиме, когда он передается в качестве аргумента, а не в общедоступном (читательском) режиме. В моем приложении мне нужно обновить объект dst с данными из объекта src, Я хочу заблокировать src поделился и dst эксклюзив. К сожалению, это может привести к тупику, если вызов другого метода обновления с src а также dst переключение происходит одновременно. Поэтому я хотел бы использовать причудливые механизмы std::scoped_lock,

Я мог бы использовать scoped_lock заблокировать оба src а также dst в эксклюзивном режиме, но эта неоправданно строгая блокировка имеет откат производительности в других местах. Однако, кажется, что это можно завернуть src"s shared_mutex в std::shared_lock и использовать это с scoped_lock: Когда scoped_lock во время его вызова блокировки действий try_lock() на shared_lockПозже позвоню try_shared_lock() на src"s shared_mutexи вот что мне нужно.

Так что мой код выглядит так просто:

struct data {
    mutable std::shared_mutex mutex;
    // actual data follows
};

void update(const data& src, data& dst)
{
    std::shared_lock slock(src.mutex, std::defer_lock);
    std::scoped_lock lockall(slock, dst.mutex);
    // now can safely update dst with src???
}

Безопасно ли использовать (совместно используемый) защитный кожух, как этот, внутри другого защитного кожуха?

1 ответ

Решение

Как отмечают различные комментаторы, которые читали код реализации стандартной библиотеки C++: да, использование std::shared_mutex завернутый в std::shared_lock() в качестве одного из аргументов std::scoped_lock() безопасно.

В основном, std::shared_lock переадресация всех звонков на lock() в lock_shared() на мьютекс.

std::shared_lock::lock -----------> mutex()->lock_shared(). // same for try_lock etc..

Другое возможное решение

std::shared_lock lk1(src.mutex, std::defer_lock);
std::unique_lock lk2(dst.mutex, std::defer_lock);
std::lock(lk1, lk2);

std::lock это функция, которая принимает любое количество Lockable все объекты и блокирует их (или отменяет за исключением, в этом случае все они будут разблокированы).

std::scoped_lock согласно cppreference является оберткой для std::lockс добавленным функционалом звонка unlock() на каждом запираемом объекте в его деструкторе. Эта дополнительная функциональность здесь не требуется, так как std::shared_lock lk1 а также std::unique_lock lk2 также работают в качестве охранников блокировки, которые открывают свои мьютексы, когда они выходят за рамки видимости.

Изменить: различные уточнения

Mutex: добавьте безопасность потоков в общие ресурсы
Блокировка: добавьте RAII (и, возможно, дополнительную функциональность) в мьютекс

Различные блокировки позволяют блокировать мьютекс разными способами:

  • unique_lock: эксклюзивный доступ к ресурсу (для записи)
  • shared_lock: общий доступ к ресурсу (для одновременного чтения)
  • scoped_lock: такой же как unique_lock, но с меньшим количеством функций

scoped_lock является эксклюзивным замком с голыми костями, который блокируется при его создании и разблокируется при его разрушении. unique_lock а также shared_lock Это эксклюзивные и разделяемые блокировки соответственно, которые также блокируются и разблокируются с помощью конструктора и деструктора по умолчанию. Тем не менее, они также обеспечивают дополнительную функциональность. Например, вы можете попытаться везти их, или вы можете разблокировать их, прежде чем они будут уничтожены.

Таким образом, типичный вариант использования будет использовать shared_lock для общего доступа (когда несколько потоков читают один и тот же ресурс) и используют unique_lock или scoped_lock для эксклюзивного доступа (в зависимости от того, нужны ли вам дополнительные функции unique_lock или нет).

Другие вопросы по тегам