如何使用lock_guard在c ++ 11中实现scoped_lock功能

时间:2018-10-25 11:51:49

标签: c++ c++11

c ++ 17中的scoped_lock看起来像是我所追求的功能,但是目前我与c ++ 11相关。

此刻,当我们多次使用同一个互斥锁调用guard_lock时,我看到死锁问题。 scoped_lock是否可以防止多次调用(即重新进入?)? 在c ++ 11 w / lock_guard中是否有最佳做法?

mutex lockingMutex;

void get(string s)
{
    lock_guard<mutex> lock(lockingMutex);
    if (isPresent(s))
    {
        //....
    }
}

bool isPresent(string s)
{
    bool ret = false;
    lock_guard<mutex> lock(lockingMutex);
    //....
    return ret;
}

2 个答案:

答案 0 :(得分:4)

要能够多次锁定同一个互斥锁,需要使用std::recursive_mutex。递归互斥比非递归互斥更为昂贵。

不过,最好的做法是设计代码,使线程不会多次锁定同一个互斥锁。例如,让您的公共函数首先锁定互斥锁,然后调用期望该互斥锁已被锁定的实现函数。实现函数不得调用锁定互斥体的公共API函数。例如:

class A {
    std::mutex m_;
    int state_ = 0;

private: // These expect the mutex to have been locked.
    void foo_() {
        ++state_;
    }

    void bar_() {
        this->foo_();
    }

public: // Public functions lock the mutex first.
    void foo() {
        std::lock_guard<std::mutex> lock(m_);
        this->foo_();
    }

    void bar() {
        std::lock_guard<std::mutex> lock(m_);
        this->bar_();
    }
};

答案 1 :(得分:0)

作用域锁不能提供您正在寻找的功能。

作用域锁只是锁卫的杂种版本。它仅由于将锁保护更改为可变模板而存在一些ABI问题。

要具有可重入互斥体,您需要使用可重入互斥体。但是,这两者在运行时都比较昂贵,并且通常表明您的互斥状态下缺乏照顾。持有互斥锁时,您应该对正在执行的所有其他同步操作有完整的了解。

一旦完全了解正在执行的所有同步操作,就很容易避免递归锁定。

您可以在此处考虑两种模式。首先,将公共锁定API与私有非锁定API分开。第二,从实现中分离同步。

private:
  mutex lockingMutex;
  bool isPresent(string s, lock_guard<mutex> const& lock) {
    bool ret = false;
    //....
    return ret;
  }
  void get(string s, lock_guard<mutex> const& lock) {
    if (isPresent(s, lock))
    {
      //....
    }
  }
public:  
  void get(string s) {
    return get( std::move(s), lock_guard<mutex>(lockingMutex) );
  }

  bool isPresent(string s) {
    return isPresent( std::move(s), lock_guard<mutex>(lockingMutex) );
  }
};

在这里,我将lock_guard<mutex>用作“证明我们有锁”。

通常更好的选择是将您的类写为非线程安全的,然后使用包装器:

template<class T>
struct mutex_guarded {
  template<class T0, class...Ts,
    std::enable_if_t<!std::is_same<std::decay_t<T0>, mutex_guarded>{}, bool> =true
  >
  mutex_guarded(T0&&t0, Ts&&...ts):
    t( std::forward<T0>(t0), std::forward<Ts>(ts)... )
  {}
  mutex_guarded()=default;
  ~mutex_guarded=default;

  template<class F>
  auto read( F&& f ) const {
    auto l = lock();
    return f(t);
  }
  template<class F>
  auto write( F&& f ) {
    auto l = lock();
    return f(t);
  }
private:
  auto lock() { return std::unique_lock<std::mutex>(m); }
  auto lock() const { return std::unique_lock<std::mutex>(m); }
  mutable std::mutex m;
  T t;
};    

现在我们可以这样使用:

mutex_guarded<Foo> foo;
foo.write([&](auto&&foo){ foo.get("hello"); } );

您可以编写mutex_gaurdedshared_mutex_guardednot_mutex_guarded甚至async_guarded(返回期货并在工作线程中序列化操作)。

只要该类在方法中不保留其自己的“控制区域”,此模式就可以使编写互斥保护的数据变得更加容易,并使您可以将相关的互斥保护的数据组合到一个包中,而不必重写它们。 / p>