Folly 的 MicroSpinLock 使用方法和实现

作者: , 共 1815 字

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

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

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

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

folly::MicroSpinLock spinlock; 

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.

类似文章:
由 Facebook 开发和维护的 C++库 Folly 提供了锁folly::MicroLock,代码文件地址:https://github.com/facebook/folly/blob/master/folly/MicroLock.h
看到网上有片段,提到没有必要自己实现自旋锁,因为标准库的 std::mutex 和现在的自旋锁的实现没有两样。比较好奇,翻了一些资料,试图找到答案。
智能指针在现代 C++里用得越多。以前只知道它大致的原理,比如使用引用计数。但很多实现细节并不清楚,最直接的,它是如何实现多线程安全的?今天找了 gnu c++ library 的实现好好看了一下。
编程 » folly, C++, 数据容器
由 Facebook 开发和维护的 C++库 Folly 提供folly::sorted_vector_setfolly::sorted_vector_map,是std::mapstd::set在小数据集上的优化版。代码见: https://github.com/facebook/folly/blob/master/folly/sorted_vector_types.h
编程 » C++, folly
folly::fbstring是一个完全兼容std::string的类,可以做到无缝替换,而且性能更高。其代码参见https://github.com/facebook/folly/blob/master/folly/FBString.h
编程 » C++, folly
高效程序总是尽量避免频繁触碰在堆上分配和释放内存,所以无论是std::string还是folly:fbstring都做了SSO( small string optimization )。而folly::FixedString是一个很有意思的实现,它可以把任意长度的字符串都放在堆上。代码可见https://github.com/facebook/folly/blob/master/folly/FixedString.h
编程 » C++, 智能指针
前面已经提到std::shared_ptr有三个缺陷:
编程 » folly, C++, 数据容器
由 Facebook 开发和维护的 C++库 Folly 提供folly::small_vector,代码文件地址:https://github.com/facebook/folly/blob/master/folly/small_vector.h
编程 » folly, C++, 数据容器
由 Facebook 开发和维护的 C++库 Folly 提供folly::small_vector,代码文件地址:https://github.com/facebook/folly/blob/master/folly/small_vector.h