一:概述
自旋锁(Spinlock) 是一种用于线程同步的锁,它通过让线程不断地自我检查("自旋")来判断是否可以获得锁,而不是让线程在等待时进入阻塞状态。这种方式使得线程能够在锁被释放之前反复检查锁的状态,从而快速尝试获取锁,而不需要进行上下文切换。
二:工作原理:
- 线程在尝试获取自旋锁时,如果发现锁没有被占用(即锁处于“空闲”状态),它会获取锁并继续执行。
- 如果锁已经被其他线程占用(即锁处于“忙碌”状态),线程不会被阻塞,而是进入一个不断检查锁状态的循环,直到锁被释放。
- 一旦锁被释放,线程立即获得锁并继续执行。
三:优缺点
优点:
- 避免上下文切换:与传统的锁(如互斥锁)不同,自旋锁不让线程进入阻塞状态。当锁已经被占用时,线程不会进入操作系统的调度队列,而是不断自旋,消除了线程上下文切换的开销。
- 适用于锁持有时间较短的情况:如果锁的持有时间很短,自旋锁可以比其他类型的锁更加高效,因为自旋等待的开销较小。
缺点:
- 忙等待导致资源浪费:如果多个线程频繁竞争锁,并且锁持有时间较长,那么自旋锁会导致CPU资源的浪费,因为线程会不断检查锁的状态,消耗了大量计算资源而没有实际的进展。
- 可能会导致活锁:如果线程在竞争自旋锁时总是处于自旋状态,可能会导致线程无法获得锁,造成活锁(即系统不断尝试某个操作,但始终无法成功)。
- 不适用于高负载的情况:在高并发的环境下,自旋锁可能会造成性能下降,因为自旋时的CPU消耗可能会比阻塞等待的成本还要高。
四:适用场景:
- 自旋锁通常适用于锁的持有时间非常短的场景。比如多核处理器上,当线程需要进行简单的原子操作时,自旋锁能够避免由于线程上下文切换带来的性能损失。
- 适用于竞争不激烈、获取锁的等待时间较短的场景。比如:一些微小的资源访问,锁定时间在纳秒到微秒级别的操作。
五:代码
#include <atomic>
#include <thread>class Spinlock{//初始化一个原子布尔类型变量 std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:void lock(){/*flag.test_and_set是一个原子操作, 它会检查 flag 的值并将其设置为 true。如果原本是 false, 它就设置为true,并返回false,否则返回 true。当 flag 被设置为 true 时,表示锁被占用,此时会不断循环直到 flag 被清除。std::memory_order_acq_rel 是一个内存顺序, 它保证lock操作之前的读写操作(例如访问共享资源)会在此锁的获取之前完成,并且在解锁时,锁后的操作会被其他线程看到。 */while( flag.test_and_set(std::memory_order_acq_rel) );}void unlock(){//释放锁的原子操作,清除atomic_flag, 将其设置为 false,其他线程就可以获得锁//std::memory_order_relase 保证了释放锁时,前面的操作会在此锁释放前可见。 flag.clear(std::memory_order_release);}};Spinlock spin;void workOnResource(){spin.lock();std::this_thread::sleep_for(std::chrono::milliseconds(2000));spin.unlock();
}int main(){std::thread t(workOnResource);std::thread t2(workOnResource);t.join();t2.join();}