c++互斥 和 std::call_once

原文链接: https://www.zhihu.com/search?type=content&q=unique_lock

在线程之间访问共享数据需要通过互斥锁来同步,保证同一时刻只有一个线程可以访问(或者只有一个线程进行写操作)。C++11起,标准库提供std::mutex以满足开发者对互斥锁的需求,相关的变体还有许多,如recursive_mutex,timed_mutex,shared_mutex等等。注意,mutex不可复制,不可移动(move)。

  1. std::recursive_mutex 是递归锁,和mutex的区别是,递归锁内部有计数器,允许同一个线程多次加解锁,只有当加解锁的次数相当,才会真正释放共享资源的占有权。注意:请勿滥用recursive_mutex。虽然看起来方便,但是会掩盖代码中的真正问题,让调试变得异常困难。如非必须,勿用。
  2. std::timed_mutex 是带有时间控制的mutex,新增了try_lock_for和try_lock_until两个方法。和mutex的try_lock相比,增加了超时判断。除此以外没有特别需要注意的。
  3. std::shared_mutex 是读写锁,提供两种访问权限的控制:共享性(shared)和排他性(exclusive)。通过lock/try_lock获取排他性访问权限,通过lock_shared/try_lock_shared获取共享性访问权限。这样的设置对于区分不同线程的读写操作特别有用。shared_mutex是c++17中引入的,使用时需要注意编译器版本。

手动使用lock/unlock来加减锁不是个好方法,因为人都会犯错,容易漏写unlock,尤其在复杂的逻辑判断中。因此标准库提供了一套RAII的机制。如lock_guard, scoped_lock,unique_lock,shared_lock等。lock_guard 和scoped_lock, 两者作用相似,都是mutex的wrapper,两者不同点是,scoped_lock支持同时lock多个mutex,而lock_guard不支持。另外xxx_lock支持手动调用lock和unlock方法,而lock_guard不支持, 听起来似乎lock _guard的行为更符合RAII的定义。实际上,scoped_lock是c++17标准中引入的,而lock_guard更像是即将被deprecated的方法。xxx_lock具有lock和unlock方法,可以在其生命周期结束前调用unlock。

unique_lock在构造时,还可以指定不同locking策略:

std::mutex g;
std::unique_lock lg(g, std::defer_lock()); // std::adopt_lock, std::try_to_lock

常见的策略包括3种:
defer_lock: 不立即取得mutex的拥有权。这种策略一般出现在需要同时lock多个mutex的时候,可以通过std::lock同时lock多个unique_lock.
try_to_lock_t: 尝试获取mutex的拥有权。
adopt_lock_t:假设线程已经在其他处获取了mutex的拥有权。

在开发过程中,有时候需要保证某个函数只被调用一次,这个需求在线程安全的单例类编写中时常会出现。传统使用mutex的写法如下,通过两次检查instance_来确保资源只申请一次。这类方法被称为 Double-Checked Locking Pattern。事实上DCLP的方法也不能解决多线程环境的共享资源保护问题(具体原因参考 Scott的C++ and the Perils of Double-Checked Locking ,中文资料参考 https://blog.csdn.net/tantexian/article/details/50684689),其主要原因是 instance_.reset(new Singleton) 并非原子操作,编译器会将其转换成三条语句来实现,这就导致了DCLP方法可能的失败。

class Singleton {
public:
  static Singleton& GetInstance() {
    if (!instance_) {
      std::lock_guard lock(mutex_);
      if (!instance_) {
        instance_.reset(new Singleton);
      }
    }
    return *instance_;
  }

  ~Singleton() = default;

private:
  Singleton() = default;

  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;

private:
  static std::unique_ptr instance_;
  static std::mutex mutex_;
};

针对这个问题,c++11 引入了 std::call_once,通过传入的std::once_flag来确保函数在多线程环境下只被执行一次,完美地解决上述问题。由此可见,语言层面的解决方案才足够简洁啊。

class Singleton {
public:
  static Singleton& GetInstance() {
    static std::once_flag s_flag;
    std::call_once(s_flag, [&]() {
      instance_.reset(new Singleton);
    });

    return *instance_;
  }

  ~Singleton() = default;

private:
  Singleton() = default;

  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;

private:
  static std::unique_ptr instance_;
};

链接:https://zhuanlan.zhihu.com/p/77999255

你可能感兴趣的:(c-c++)