web-dev-qa-db-ja.com

std :: mutexがロックされている場合のアサート方法は?

GCC 4.8.2(Linux/Debian/Sid 64ビット)-またはGCC 4.9(利用可能な場合)-C++ 11の場合-いくつかのmutex

std::mutex gmtx;

実際には、以下のstaticメソッドとFooメソッドの両方を含むクラスalphabetaメンバーです。

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_lockedassert内でのみ呼び出されることに注意してください...これは非常に非効率的または不正確な場合さえあります)

もちろん、betaを呼び出す他の関数があります。

void gamma(void) {
   std::lock_guard<std::mutex> g(gmtx);
   beta();
   // some other work
}

しかしis_lockedは存在しません。..どうすれば定義できますか? (実際には、ミューテックスが同じスレッドで[間接]呼び出し元によってロックされていることを確認したい...)

assertでテストしたいのは、betaが別の場所で呼び出される可能性があるためです)

try_lockは使用できません(再帰的ミューテックスを使用しない限り)。これは、一般的なケースでは、既にロックされているミューテックスをロックするためです(呼び出し側によって同じスレッドでロックされます)。これは未定義の動作だけでなく、完全にブロックされます。 。

本当に必要な場合を除いて、再帰的なミューテックス(プレーンなミューテックスよりもコストがかかる)は避けたいです。


注意:実際のプログラムはもう少し複雑です。実際には、すべてのメソッドは、「アイテム」に関する命名の双方向関係を維持するクラスの内部にあります。したがって、そのクラスの内部には、アイテムから名前へのマップと、名前からアイテムへのマップがあります。 betaは、実際に名前を追加する内部メソッドであり、alphaおよびgammaは、名前でアイテムを検索または追加するメソッド、または名前でアイテムを追加するメソッドです項目。

PS:実際のプログラムはまだリリースされていませんが、 [〜#〜] melt [〜#〜] の一部になるはずです---その未来 monitor ; here (一時的な場所)からダウンロードできます(アルファ段階、非常にバグが多い)。

25

_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
}
_

編集:このソリューションでは、すべてのロック操作は、「生の」ミューテックスglockではなく、unique_lock gmtxを介して実行する必要があります。たとえば、alphaメンバー関数は_lock_guard<unique_lock<mutex>>_(または単にlock_guard<decltype(glock)>)で書き換えられます。

15
yohjp

厳密に言えば、問題は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に対するオーバーヘッドがゼロです。ただし、構築/破壊の場合を除きます(mutexオブジェクトの場合は問題ではありません)。
  2. m_holderメンバーは、ミューテックスを取得してから解放するまでの間にのみアクセスされます。したがって、ミューテックス自体がm_holderのミューテックスとして機能します。タイプstd::thread::idについての非常に弱い想定では、locked_by_callerは正しく機能します。
  3. 他のSTLコンポーネント(例:std::lock_guard)はテンプレートであるため、この新しいクラスで適切に機能します。
17
Ami Tavory

同じスレッドで複数回ロックできるrecursive_mutexを使用できます。注:それが私のコードである場合は、recursive_mutexを必要としないように再構成しますが、問題は解決されます。

9
Nevin

atomic を試してください(例:atomic<bool>またはatomic<int>)には、必要な機能を実行するNice load 関数と、compare_exchange_strongなどの他のNice関数があります。

2
Andrew

まあ、アサーションの費用が本当に問題でない場合は、その動作が正しく定義されていることが保証されている別のスレッドから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
}
1
bames53

私のソリューションはシンプルです。try_lockを使用してテストし、必要に応じてロックを解除します。

std::mutex mtx;

bool is_locked() {
   if (mtx.try_lock()) {
      mtx.unlock();
      return false;
   }
   return true; // locked thus try_lock failed
}
0
Tony