自旋锁
定义
自旋锁是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,
那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
自旋锁与互斥锁:
- 自旋锁与互斥锁都是为了实现保护资源共享的机制。
- 无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。
- 获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。
自旋锁总结:
- 线程获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。
- 自旋锁等待期间,线程的状态不会改变,线程一直是用户态并且是活动的(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;
}