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

What is the best way to lock object by private mutex?

本文关键字:对象 最佳 方法 是什么 锁定      更新时间:2023-10-16

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

#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复制构造器被删除。我怎么能做这样的事情?

只需改用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的不变性是它始终保持互斥锁的锁。

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 表达式调用它将为它提供最好的机会,使其没有任何开销。

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