Как утверждать, заблокирован ли std:: mutex?
С GCC 4.8.2 (на 64-разрядных версиях Linux/Debian/Sid) -или GCC 4.9, если доступно - на С++ 11- У меня есть некоторый мьютекс
std::mutex gmtx;
фактически, это член static
в некотором классе Foo
, содержащий как методы alpha
, так и beta
ниже.
он заблокирован в alpha
как
void alpha(void) {
std::lock_guard<std::mutex> g(gmtx);
beta(void);
// some other work
}
и я хочу проверить beta
, что действительно gmtx
заблокировано:
void beta(void) {
assert (gmtx.is_locked());
// some real work
}
(заметим, что is_locked
вызывается только внутри assert
... Он может быть очень неэффективным или даже иногда неточным)
Конечно, у меня есть другие функции, вызывающие beta
, например.
void gamma(void) {
std::lock_guard<std::mutex> g(gmtx);
beta();
// some other work
}
но is_locked
не существует.... Как его определить? (на самом деле я хотел бы быть уверенным, что мьютекс заблокирован в том же потоке некоторым [косвенным] вызывающим абонентом...)
(причина, по которой я хочу проверить, что с assert
заключается в том, что beta
можно было бы назвать в другом месте)
Я не могу использовать try_lock
(если не использовать рекурсивные мьютексы), потому что в общем случае он блокирует уже заблокированный мьютекс... (заблокирован в том же потоке вызывающим), и это не только поведение undefined но полностью блокирует.
Я хочу избежать рекурсивных мьютексов (более дорогостоящих, чем обычные мьютексы), если мне действительно не нужно.
NB: реальная программа немного сложнее. Фактически, все методы находятся внутри класса, которые поддерживают двунаправленное отношение имен к "элементам". Поэтому у меня внутри класса есть карта от элементов к именам, а другая - от имен к элементам. beta
будет внутренним методом, добавляющим действительно именование, а alpha
и gamma
будут определять методы -или добавление элемента по его имени или имя по его элементу.
PS: реальная программа еще не выпущена, но должна стать частью MELT - ее будущее монитор; вы можете скачать его (альфа-сцена, очень багги) из здесь (временное местоположение)
Ответы
Ответ 1
std::unique_lock<L>
имеет owns_lock
функцию-член (эквивалент is_locked
, как вы говорите).
std::mutex gmtx;
std::unique_lock<std::mutex> glock(gmtx, std::defer_lock);
void alpha(void) {
std::lock_guard<decltype(glock)> g(glock);
beta(void);
// some other work
}
void beta(void) {
assert(glock.owns_lock()); // or just assert(glock);
// some real work
}
EDIT:. В этом решении все операции блокировки должны выполняться через unique_lock glock
not 'raw' mutex gmtx
. Например, функция члена alpha
переписывается с помощью lock_guard<unique_lock<mutex>>
(или просто lock_guard<decltype(glock)>
).
Ответ 2
Строго говоря, речь шла о проверке блокировки std::mutex
напрямую. Однако, если разрешено его инкапсулирование в новый класс, это очень легко сделать:
class mutex :
public std::mutex
{
public:
#ifndef NDEBUG
void lock()
{
std::mutex::lock();
m_holder = std::this_thread::get_id();
}
#endif // #ifndef NDEBUG
#ifndef NDEBUG
void unlock()
{
m_holder = std::thread::id();
std::mutex::unlock();
}
#endif // #ifndef NDEBUG
#ifndef NDEBUG
/**
* @return true iff the mutex is locked by the caller of this method. */
bool locked_by_caller() const
{
return m_holder == std::this_thread::get_id();
}
#endif // #ifndef NDEBUG
private:
#ifndef NDEBUG
std::atomic<std::thread::id> m_holder;
#endif // #ifndef NDEBUG
};
Обратите внимание на следующее:
- В режиме релиза это имеет нулевые накладные расходы по сравнению с
std::mutex
за исключением, возможно, строительства/разрушения (что не является проблемой для объектов мьютекса). -
m_holder
члену m_holder
осуществляется только между взятием мьютекса и его освобождением. Таким образом, сам мьютекс служит мьютексом m_holder
. С очень слабыми предположениями о типе std::thread::id
, locked_by_caller
будет работать правильно. - Другие компоненты STL, например,
std::lock_guard
являются шаблонами, поэтому они хорошо работают с этим новым классом.
Ответ 3
Вы можете просто использовать recursive_mutex
, который можно заблокировать несколько раз в одном потоке. Примечание. Если бы это был мой код, я бы реструктурировал его, чтобы мне не нужен recursive_mutex
, но он будет решать вашу проблему.
Ответ 4
Попробуйте atomic (например, atomic<bool>
или atomic<int>
), который имеет приятный load функция, которая будет делать то, что вы хотите, а также другие приятные функции, такие как compare_exchange_strong.
Ответ 5
Поскольку ваш вопрос: "Как утверждать, что мьютекс заблокирован?", вы можете использовать assert(!mtx.try_lock());
. Конечно, вы можете только утверждать, что мьютекс заблокирован любым потоком, но не этим потоком.
Ответ 6
Хорошо, если расходы на утверждение действительно не являются проблемой, вы можете просто вызвать try_lock()
из другого потока, где его поведение гарантированно будет определено:
void beta(void) {
assert(std::async(std::launch::async, [] { return gmtx.try_lock(); })
.get() == false &&
"error, beta called without locking gmtx");
// some real work
}