14

Folly 的 MicroSpinLock 使用方法和实现

 3 years ago
source link: https://zhiqiang.org/coding/folly-micro-spinlock.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Folly 的 MicroSpinLock 使用方法和实现

作者: 张志强

, 发表于 2019-12-31

, 共 1908 字 , 共阅读 342 次

由 Facebook 开发和维护的 C++库 Folly 提供了自旋锁的实现folly::MicroSpinLock,代码文件地址:https://github.com/facebook/folly/blob/master/folly/synchronization/MicroSpinLock.h

folly::MicroSpinLock小到只有一个字节,并且为POD类型,也就是说它可以保存到文件,在(共享)内存中共享!

在使用前必须初始化spin.init()。由于是 POD 的,也可以直接置 0 初始化,也就是在一个大对象里memset为 0 也符合初始化条件。

根据 Facebook 的测试,该自旋锁单次耗时约 13.5ns ,std::mutex性能大约在 25ns ,参考的虚函数单次调用耗时约 1.7ns。

它的使用很简单,就是lockunlock函数,也可以用std::lock_guard来自动上锁和解锁(folly提供MSLGuardtypedef):

folly::MicroSpinLock spinlock; 

spinlock.init();

spinlock.lock();
// your work need to protect
spinlock.unlock();

{
    // 已定义typedef std::lock_guard<MicroSpinLock> MSLGuard  
    MSLGuard guard{spinlock};
    // your work need to protect
}

为了达到POD的目的,folly::MicroSpinLock只有一个uint8_t类型的成员lock_。但每次使用时,会强行转换成std::atomic对象:

std::atomic<uint8_t>* payload() noexcept {
    return reinterpret_cast<std::atomic<uint8_t>*>(&this->lock_);
}

我们细看lock的实现:

void lock() noexcept {
  detail::Sleeper sleeper;
  while (!cas(FREE, LOCKED)) {
    do {
      sleeper.wait();
    } while (payload()->load(std::memory_order_relaxed) == LOCKED);
  }
  assert(payload()->load() == LOCKED);
  annotate_rwlock_acquired(
      this, annotate_rwlock_level::wrlock, __FILE__, __LINE__);
}

这里面有三个地方需要展开说。

第一个是cas,这个是std::atomic提供的标准比较替换的原子实现:

bool cas(uint8_t compare, uint8_t newVal) noexcept {
  return std::atomic_compare_exchange_strong_explicit(
      payload(),
      &compare,
      newVal,
      std::memory_order_acquire,
      std::memory_order_relaxed);
}

这里用了对内存顺序要求最高的std::atomic_compare_exchange_strong_explicit。具体原因和对效率的影响不太清楚。

第二个是用了detail::Sleeper。最终实现了,前 4000 次检查失败时,会插入_mm_pause空指令,但 4000 次之后,将休眠 0.5 毫秒,此时将让出 CPU ,线程被切换,避免总是占住 CPU。

因为每次只插入一条pause空指令,所以 4000 次检查,不一定够。但不提供修改方式(除了改源代码)。

第三个是最后还调用了annotate_rwlock_acquired这个函数。这个用于配合线程检查工具ThreadSanitizer,检查多线程编程中是否有Data Race现象。

当用户没有开启选项时,该函数会被编译器优化掉,因此不用担心该行代码会降低性能。

Q. E. D.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK