引言
因为c++提供了很多的锁,信号量,还有辅助创建锁的工具,但是没有提供跨平台性很好的自旋锁,所以我们利用辅助创建锁的工具,只需要提供lock(),try_lock(),unlock(),三个API,这样子就可以创建出我们的自旋锁。
自旋锁
自旋锁(SpinLock)就是:线程抢不到锁时,不进入休眠,而是在 CPU 上不停地循环检查,直到锁被释放
自旋锁一般要配合CPU的优化,因为不停的忙等会导致CPU的性能下降,所以我们需要告诉CPU这个地方不需要一直忙等,而是时不时来看一眼。
inline void cpu_relax() noexcept { #if defined (__x86_64__) || defined (__i386__) _mm_pause(); #endif }这个是我们自己写的自旋锁
我们的锁是std::atomic_flag,一般实现自旋锁就是这个,因为这个只可以设置false / true
解释一下lock()函数,外层循环里面查看锁是否可以拥有,如果是false,那么就设置为true,并且返回原来的状态false,这个样子不仅仅表示拥有了锁而且跳出了循环。当第二个线程来的时候,因为被设置为了true,所以返回true,并且不会改变锁的状态。进入内部的循环,并且因为如果外部没有释放锁,那么一直是true,所以内部一直循环,里面也做了CPU的优化。当外部锁被释放,跳出内部循环,重新进入外部循环,然后跳出外部循环。
class SpinLock { public: using Lock = std::lock_guard<SpinLock>; SpinLock() noexcept = default; ~SpinLock() noexcept = default; SpinLock(const SpinLock&) = delete; SpinLock& operator=(const SpinLock&) = delete; void lock() noexcept { while(mutex_.test_and_set(std::memory_order_acquire)) { while(mutex_.test(std::memory_order_relaxed)) { cpu_relax(); } } } // 不可以忽略掉返回值,也就是要bool a = try_lock(); [[nodiscard]] bool try_lock() noexcept { return !mutex_.test_and_set(std::memory_order_acquire); } void unlock() noexcept { mutex_.clear(std::memory_order_release); } private: std::atomic_flag mutex_ = ATOMIC_FLAG_INIT; // 0 };最后我们使用的时候只需要传入我们的SpinLock即可,因为辅助的创建锁工具是一个模板类,这个类里面只要实现了特定的接口那么就可以使用这个锁。
template <typename Mutex> std::chrono::nanoseconds BenchmarkMutex(std::size_t thread_count) { Mutex mutex; Counter_ = 0; const auto elapsed = BenchmarkThreads(thread_count, [&](std::size_t, u64 operations) { for(u64 index = 0; index < operations; index++) { std::lock_guard<Mutex> lock(mutex); Counter_++; } }); if (Counter_ != target_) { throw std::runtime_error("mutex correctness check failed"); } return elapsed; }总结
本篇文章到这里就结束了!!!希望可以帮助大家理解~~~