Как утверждать, заблокирован ли 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
};

Обратите внимание на следующее:

  1. В режиме релиза это имеет нулевые накладные расходы по сравнению с std::mutex за исключением, возможно, строительства/разрушения (что не является проблемой для объектов мьютекса).
  2. m_holder члену m_holder осуществляется только между взятием мьютекса и его освобождением. Таким образом, сам мьютекс служит мьютексом m_holder. С очень слабыми предположениями о типе std::thread::id, locked_by_caller будет работать правильно.
  3. Другие компоненты 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
}