自旋锁简介

自旋锁通过 CAS(Compare-And-Swap)操作,在大多数场景下可以实现比 std::mutex 更高的性能。其基本思想是:通过原子操作让所有线程竞争同一个原子变量,以此实现互斥。

性能对比

无竞争情况下:

  1. spin_lock:16000W 次/秒
  2. mutex:2800W 次/秒
  3. std::atomic<int>:24100W 次/秒

可见自旋锁的性能相当可观。

5 个线程竞争情况下:

  1. spin_lock:717W 次/秒
  2. mutex:509W 次/秒
  3. atomic<int>:5900W 次/秒

高竞争场景下,自旋锁的性能依然高于 mutex。

实现代码

以下是基础版本的实现。调整原子操作的内存顺序可以进一步提升性能,这里暂时使用默认方式。

/*
                Email me@dreamyouxi.com
                
                自旋锁
                
                */
                #pragma  once
                #include <atomic>
                #include <thread>
                #include <chrono>
                
                //cas
                class spin_lock
                {
                private:
                	std::atomic<bool> _internal_tag = false;
                private:
                	spin_lock(const spin_lock &) = delete;
                	spin_lock& operator = (const spin_lock&) = delete;
                public:
                	void lock();
                	void unlock();
                };
                
                class spin_lock_guard
                {
                	spin_lock & locker;// ref
                public:
                	spin_lock_guard(spin_lock& lock);
                	~spin_lock_guard();
                };
                #include "util/log.h"
                #include "concurrency/spin_lock.h"
                const char LOG_NAME[] = "spin_lock";
                using namespace  std;
                
                void spin_lock::lock()
                {
                	bool except = false;
                	int times = 0;
                	//fast pass
                	while (true)
                	{
                		if (_internal_tag.compare_exchange_strong(except, true, std::memory_order_seq_cst))
                		{
                			//ok got it
                			return;
                		}
                		if (++times > 4000)
                		{
                			//
                			break;
                		}
                		except = false;
                	}
                	times = 0;
                	//slow pass
                	while (true)
                	{
                		if (_internal_tag.compare_exchange_strong(except, true, std::memory_order_seq_cst))
                		{
                			//ok got it
                			return;
                		}
                		if (++times < 1000)
                		{
                			//
                			this_thread::yield();
                		}
                        else
                        {
                           break;
                        }
                		except = false;
                	}
                	//
                	//very slow pass
                	while (true)
                	{
                		if (_internal_tag.compare_exchange_strong(except, true, std::memory_order_seq_cst))
                		{
                			//ok got it
                			return;
                		}
                		except = false;
                		//内存延时 6代E5 大概是100纳秒一个周期
                		//mutex 无竞争情况下 性能大概是每秒300W 次 即 0.3微秒 300纳秒
                		//sleep_for 进度在win平台好像最小是1MS 因此这里的设定也许有BUG
                		this_thread::sleep_for(std::chrono::nanoseconds(300));
                	}
                }
                
                void  spin_lock::unlock()
                {
                	_internal_tag.store(false, std::memory_order_seq_cst);
                }
                
                spin_lock_guard::spin_lock_guard(spin_lock& lock) :
                locker(lock)
                {
                	locker.lock();
                }
                
                spin_lock_guard::~spin_lock_guard()
                {
                	locker.unlock();
                }

最高性能版本

/*
                Email me@dreamyouxi.com
                
                自旋锁
                
                */
                #pragma  once
                #include <atomic>
                #include <thread>
                #include <chrono>
                
                //cas
                class spin_lock
                {
                	std::atomic_flag locker = ATOMIC_FLAG_INIT;  // NOLINT
                public:
                	spin_lock(const spin_lock &) = delete;
                	spin_lock& operator = (const spin_lock&) = delete;
                
                	spin_lock() = default;
                
                	void unlock()
                	{
                		locker.clear(std::memory_order_release);
                	}
                	void lock();
                private:
                	bool try_lock()
                	{
                		bool ok = locker.test_and_set(std::memory_order_acquire);
                		return !ok;
                	}
                };
                
                class spin_lock_guard
                {
                	spin_lock & locker;// ref
                public:
                	spin_lock_guard(spin_lock& lock);
                	~spin_lock_guard();
                };
                
                
                
                #include "util/log.h"
                #include "concurrency/spin_lock.h"
                const char LOG_NAME[] = "spin_lock";
                using namespace  std;
                
                void spin_lock::lock()
                {
                	int times = 0;
                	//fast pass
                	while (true)
                	{
                		if (try_lock())
                		{
                			//ok got it
                			return;
                		}
                		if (++times > 4000)
                		{
                			//
                			break;
                		}
                	}
                	times = 0;
                	//slow pass
                	while (true)
                	{
                		if (try_lock())
                		{
                			//ok got it
                			return;
                		}
                		if (++times < 1000)
                		{
                			//
                			this_thread::yield();
                		}
                        else
                        {
                           break;
                        }
                	}
                	//
                	//very slow pass
                	while (true)
                	{
                		if (try_lock())
                		{
                			//ok got it
                			return;
                		}
                		//内存延时 6代E5 大概是100纳秒一个周期
                		//mutex 无竞争情况下 性能大概是每秒300W 次 即 0.3微秒 300纳秒
                		//sleep_for 进度在win平台好像最小是1MS 因此这里的设定也许有BUG
                		this_thread::sleep_for(std::chrono::nanoseconds(300));
                	}
                }
                
                spin_lock_guard::spin_lock_guard(spin_lock& lock) :
                locker(lock)
                {
                	locker.lock();
                }
                
                spin_lock_guard::~spin_lock_guard()
                {
                	locker.unlock();
                }