C++ 如何断言std::mutex是否已锁定?

C++ 如何断言std::mutex是否已锁定?,c++,linux,gcc,c++11,C++,Linux,Gcc,C++11,对于GCC4.8.2(在Linux/Debian/Sid 64位上)或GCC4.9(如果可用的话),在C++11中,我有一些互斥锁 std::mutex gmtx; 实际上,它是某个类Foo中的static成员,包含下面的alpha和beta方法 它被锁定在alpha中,就像 void alpha(void) { std::lock_guard<std::mutex> g(gmtx); beta(void); // some other work } (请注意,

对于GCC4.8.2(在Linux/Debian/Sid 64位上)或GCC4.9(如果可用的话),在C++11中,我有一些互斥锁

std::mutex gmtx;
实际上,它是某个类
Foo
中的
static
成员,包含下面的
alpha
beta
方法

它被锁定在
alpha
中,就像

void alpha(void) {
   std::lock_guard<std::mutex> g(gmtx);
   beta(void);
   // some other work
}
(请注意,
被锁定
仅在
断言
中调用……它可能效率很低,甚至有时不准确)

当然,我还有其他函数调用
beta
,例如

void gamma(void) {
   std::lock_guard<std::mutex> g(gmtx);
   beta();
   // some other work
}
void伽马(void){
标准:锁紧装置g(gmtx);
β();
//其他工作
}
但是
已锁定
不存在。。。。我应该如何定义它?(实际上,我想确定互斥锁已被某个[间接]调用方锁定在同一线程中…)

(我想用
assert
测试它的原因是
beta
可以在别处调用)

我不能使用
try\u lock
(除非使用递归互斥锁),因为在常见情况下,它会锁定一个已经锁定的互斥锁。。。(被调用者锁定在同一个线程中)这不仅是未定义的行为,而且完全阻塞

我希望避免递归互斥(比普通互斥代价更高),除非我真的必须这样做


注意:真正的程序有点复杂。实际上,所有的方法都在一个类中,该类在“items”上保持命名双向关系。所以我在这个类中有一个从项目到名称的映射,还有一个从名称到项目的映射
beta
将是真正添加命名的内部方法,
alpha
gamma
将是按项目名称查找或添加项目或按项目名称查找名称的方法


PS:真正的程序尚未发布,但应该成为其未来的一部分;您可以从(临时位置)下载它(alpha-stage,非常有缺陷)

您可以使用一个递归互斥锁,它可以在同一个线程上被多次锁定。注意:如果它是我的代码,我会重新构造它,这样我就不需要一个
递归互斥锁了,但它可以解决您的问题。

好吧,如果断言的开销真的不是问题,那么您可以从另一个线程调用
try\u 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
}

std::unique_lock
具有
拥有_lock
成员函数(正如您所说,相当于
已锁定

std::互斥gmtx;
std::唯一锁定glock(gmtx,std::延迟锁定);
空洞alpha(空洞){
标准:锁紧护罩g(格洛克);
β(无效);
//其他工作
}
无效测试版(无效){
assert(glock.owns_lock());//或只是assert(glock);
//一些真正的工作
}

编辑:在此解决方案中,所有锁定操作都应通过unique_lock
glock
而不是“原始”互斥锁
gmtx
执行。例如,
alpha
成员函数用
lock\u-guard
(或者简单地说是
lock\u-guard
)重写。

严格来说,问题是直接检查
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::互斥
{
公众:
#ifndef NDEBUG
无效锁()
{
std::mutex::lock();
m_holder=std::this_thread::get_id();
}
#endif/#ifndef NDEBUG
#ifndef NDEBUG
无效解锁()
{
m_holder=std::thread::id();
std::mutex::unlock();
}
#endif/#ifndef NDEBUG
#ifndef NDEBUG
/**
*@return true如果互斥锁被此方法的调用方锁定*/
布尔被调用方()常量锁定
{
返回m_holder==std::this_thread::get_id();
}
#endif/#ifndef NDEBUG
私人:
#ifndef NDEBUG
标准::原子m_支架;
#endif/#ifndef NDEBUG
};
注意以下几点:

  • 在发布模式下,除了可能的构造/销毁(这对于互斥对象来说不是问题)之外,这在std::mutex上没有任何开销
  • 只有在获取互斥锁和释放互斥锁之间才能访问
    m_holder
    成员。因此,互斥锁本身充当
    m_holder
    的互斥锁。由于对类型
    std::thread::id
    的假设非常弱,调用方锁定的
    将正常工作
  • 其他STL组件,例如,
    std::lock\u guard
    都是模板,因此它们可以很好地与这个新类配合使用

  • 试试(例如,
    atomic
    atomic
    ),它有一个很好的功能,可以做你想做的事情,还有其他很好的功能,比如compare\u exchange\u strong。

    我的解决方案很简单,使用Try\u lock进行测试,然后在需要时解锁:

    std::mutex mtx;
    
    bool is_locked() {
       if (mtx.try_lock()) {
          mtx.unlock();
          return false;
       }
       return true; // locked thus try_lock failed
    }
    

    从技术上讲,这不是一个断言,但我使用了一种类似的方法来防止对共享状态的解锁访问:在不安全函数(在您的示例中为beta)中的上为lock guard类添加一个引用参数。然后,除非调用方创建了锁保护,否则无法调用该函数。它解决了在锁之外意外调用函数的问题,并且在编译时这样做,没有争用

    因此,以你的例子:

    typedef std::lock_guard<std::mutex> LockGuard;
    void alpha(void) {
       LockGuard g(gmtx);
       beta(g);
       // some other work
    }
    
    void beta(LockGuard&) {
       // some real work
    }
    
    void gamma(void) {
       LockGuard g(gmtx);
       beta(g);
       // some other work
    }
    
    //works recursively too
    void delta(LockGuard& g)
    {
       beta(g);
    }
    
    typedef std::lock_-guard LockGuard;
    空洞alpha(空洞){
    锁具g(gmtx);
    β(g);
    //其他工作
    }
    无效测试版(锁具和){
    //一些真正的工作
    }
    空隙伽马(空隙){
    锁具g(gmtx);
    β(g);
    //其他工作
    }
    //也递归地工作
    void delta(锁具和g)
    {
    β(g);
    }
    
    缺点:

    • 不验证锁是否实际包装了正确的互斥锁
    • 需要伪参数。实际上,我通常将这些不安全的函数保密,因此这不是问题

    如果
    beta
    要求始终锁定互斥锁(正如您的
    断言
    建议的那样),您为什么要将这一责任推到调用链的上游?因为(正如我编辑的)其他一些函数,如
    gamma
    会调用
    beta
    …对吗
    std::mutex mtx;
    
    bool is_locked() {
       if (mtx.try_lock()) {
          mtx.unlock();
          return false;
       }
       return true; // locked thus try_lock failed
    }
    
    typedef std::lock_guard<std::mutex> LockGuard;
    void alpha(void) {
       LockGuard g(gmtx);
       beta(g);
       // some other work
    }
    
    void beta(LockGuard&) {
       // some real work
    }
    
    void gamma(void) {
       LockGuard g(gmtx);
       beta(g);
       // some other work
    }
    
    //works recursively too
    void delta(LockGuard& g)
    {
       beta(g);
    }