C++ 并发编程:自旋锁

自旋锁

定义

自旋锁是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,
那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

自旋锁与互斥锁:

  • 自旋锁与互斥锁都是为了实现保护资源共享的机制。
  • 无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。
  • 获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。

自旋锁总结:

  • 线程获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。
  • 自旋锁等待期间,线程的状态不会改变,线程一直是用户态并且是活动的(active)。
  • 自旋锁如果持有锁的时间太长,则会导致其它等待获取锁的线程耗尽CPU。
  • 自旋锁本身无法保证公平性,同时也无法保证可重入性。
  • 基于自旋锁,可以实现具备公平性和可重入性质的锁

两种锁适用于不同场景:

  • 如果是多核处理器,如果预计线程等待锁的时间很短,短到比线程两次上下文切换时间要少的情况下,使用自旋锁是划算的。
  • 如果是多核处理器,如果预计线程等待锁的时间较长,至少比两次线程上下文切换的时间要长,建议使用互斥量。
  • 如果是单核处理器,一般建议不要使用自旋锁。因为,在同一时间只有一个线程是处在运行状态,那如果运行线程发现无法获取锁,只能等待解锁,但因为自身不挂起,所以那个获取到锁的线程没有办法进入运行状态,只能等到运行线程把操作系统分给它的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高。
  • 如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大。

总结:

  • 非必要,尽量不要用自旋锁(难以实现合理高效的自旋锁)
  • 同一个线程2次lock(),很可能会死锁.

简单的自旋锁例子

#include "pch.h"
#include <iostream>
#include <atomic>
#include <thread>
#include <chrono>

using namespace std;

class SpinLock {
public:	
	inline void Lock() {
		while (m_flag.test_and_set()) {
			std::this_thread::yield();
		}
	}
	inline void UnLock() { m_flag.clear(); }
private:
	std::atomic_flag m_flag = ATOMIC_FLAG_INIT;
};

void fun1(int i,SpinLock& lock) {
	while (1) {
		lock.Lock();
		std::cout << "threadId:" << i << ":starting" << std::endl;
		std::this_thread::sleep_for(std::chrono::microseconds(100));
		lock.UnLock();
	}
	
}

void fun2(int i,SpinLock& lock) {
	while (1) {
		lock.Lock();
		std::cout << "threadId:" << i << ":starting" << std::endl;
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
		lock.UnLock();
	}
}

int main()
{
	SpinLock lock;
	std::thread t1(fun1,1,std::ref(lock));
	std::thread t2(fun2, 2, std::ref(lock));
	t1.join();
	t2.join();
	return 0;
}
发布了155 篇原创文章 · 获赞 15 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/wangdamingll/article/details/104432321
今日推荐