由 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。
它的使用很简单,就是lock
和unlock
函数,也可以用std::lock_guard
来自动上锁和解锁(folly
提供MSLGuard
的typedef
):
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.