通过私有互斥锁锁定对象的最佳方法是什么?

时间:2016-01-23 18:28:24

标签: c++ multithreading c++11 mutex

我需要在某些外部函数中通过私有互斥锁来锁定对象。最好的方法是什么?
我想要这样的东西

#include <thread>
#include <mutex>

class Test
{
public:
    std::lock_guard<std::mutex> lockGuard()
        {
            return std::lock_guard<std::mutex>(mutex);
        }

private:
    std::mutex mutex;
};

int main()
{
    Test test;
    std::lock_guard<std::mutex> lock = test.lockGuard();
    //...
}

但删除了lock_guard复制构造函数。我怎么能这样做?

2 个答案:

答案 0 :(得分:2)

只需使用std::unique_lock<std::mutex>即可。 可复制,但 可移动,允许您显示的模式。

#include <thread>
#include <mutex>

class Test
{
public:
    std::unique_lock<std::mutex> lockGuard()
        {
            return std::unique_lock<std::mutex>(mutex);
        }

private:
    std::mutex mutex;
};

int main()
{
    Test test;
    std::unique_lock<std::mutex> lock = test.lockGuard();
    //...
}

std::unique_lock<std::mutex>相对于std::lock_guard具有更广泛的API,包括:

  • 移动构造。
  • 移动可分配。
  • 可交换。
  • 锁()
  • 解锁()
  • try_lock()
  • try_lock_for()
  • try_lock_until()
  • 释放()
  • owns_lock()

换句话说,由于您可以从unique_lock解锁并移动,因此无法保证锁定在互斥锁上(您可以检查它是否与owns_lock()一起)。相比之下,lock_guard的不变量是它始终锁定互斥锁。

答案 1 :(得分:1)

std::unique_lock<T>定义了一个移动构造函数,可以根据需要使用,但这种方法本身并不是很成功。 您应该检查锁定粒度,通常如果您无法在对象上执行操作(或需要执行多个操作)时提供内部同步并要求用户保持锁定,则没有理由将互斥锁存储在内部对象。

如果我必须将互斥锁存储在对象中,我会使用一些包装器,它允许我执行以下操作:

locking_wrapper<Test> test;
test.do_locked([] (Test & instance) {
    /* The following code is guaranteed not to interleave with
     * any operations performed on instance from other threads. */
    // your code using instance here
});

locking_wrapper<T>将存储对象的实例并提供对它的引用,同时保持对内部互斥锁的锁定。依靠编译器内联代码的能力,这种方法不应该超出你在问题中尝试做的任何开销。

实施locking_wrapper的一般想法如下:

template<typename T>
class locking_wrapper
{
    mutable std::mutex mutex;
    // the object which requires external synchronization on access
    T instance;

public:
    /* Here we define whatever constructors required to construct the
     * locking_wrapper (e.g. value-initialize the instance, take an 
     * instance passed by user or something different) */
    locking_wrapper() = default;
    locking_wrapper(const T & instance) : instance{instance} {}

    // Takes a functor to be performed on instance while maintaining lock
    template<typename Functor>
    void do_locked(Functor && f) const {
        const std::lock_guard<std::mutex> lock{mutex};
        f(instance);
    }
};

您可以根据需要将任何可调用实体传递给do_locked,但是我之前建议使用lambda-expression调用它将为其提供内联的最佳机会,而不会产生任何开销。

请注意,使用此方法与引用,可移动对象或我尚未预见的其他类型将需要对代码进行一些修改。