自旋锁和互斥锁都是应用在多线程中处理竞争资源的,实现线程同步的机制。他们的作用是相似的,但是原理和工作方式有所不同。
基本概念:
--1 互斥锁:std::mutex
c++ 提供了关于互斥锁的封装 例如std::unique_lock 和 std::lock_guard便于使用
--2 自旋锁:
在 c++ 中,标准库没有直接提供自旋锁(spinlock),通常我们会结合原子量来实现。
--3 对比:
特性 | 自旋锁 | 互斥锁 |
获取锁方式 | 自旋(忙等待),不断检查锁状态 | 阻塞,线程等待直到锁可用 |
性能 | 在锁持有时间短时性能较好 | 在锁持有时间长时性能较好 |
适用场景 | 锁持有时间短,锁竞争较少的场景 | 锁持有时间长或锁竞争较多的场景 |
CPU 使用 | 会消耗 CPU 资源(自旋等待) | 无 CPU 消耗,线程进入阻塞状态 |
内存管理 | 不需要系统调用,轻量级 | 需要系统调用,较重 |
使用复杂度 | 简单,直接使用原子操作 | 稍复杂,通常使用操作系统提供的 API |
所以使用的时候需要评估是切换CPU上下文耗时还是临时等待耗时。
实现:
互斥锁:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 声明一个互斥锁
int shared_data = 0; // 共享资源
// 线程函数
void increment() {
// 加锁
mtx.lock();
++shared_data; // 修改共享资源
std::cout << "Thread " << std::this_thread::get_id() << " incremented shared_data to " << shared_data << std::endl;
// 解锁
mtx.unlock();
}
int main() {
// 创建多个线程
std::thread t1(increment);
std::thread t2(increment);
std::thread t3(increment);
// 等待所有线程完成
t1.join();
t2.join();
t3.join();
std::cout << "Final shared_data: " << shared_data << std::endl;
return 0;
}
自旋锁:
#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
class SpinLock {
public:
explicit SpinLock(uint32_t spin_limit = 1000)
: flag_(false), spin_limit_(spin_limit) {}
// Lock the SpinLock (blocking with spin limit)
void lock() {
uint32_t spin_count = 0;
while (true) {
// Try to acquire the lock
if (!flag_.exchange(true, std::memory_order_acquire)) {
return; // Lock acquired
}
// If spin limit is exceeded, yield the CPU
if (++spin_count >= spin_limit_) {
spin_count = 0; // Reset the counter
std::this_thread::yield();
}
}
}
// Try to lock the SpinLock without blocking
bool try_lock() { return !flag_.exchange(true, std::memory_order_acquire); }
// Unlock the SpinLock
void unlock() { flag_.store(false, std::memory_order_release); }
private:
std::atomic<bool> flag_;
uint32_t spin_limit_;
};
SpinLock lock(1000); // 自旋限制为 1000 次
void critical_section(int id) {
lock.lock();
std::cout << "Thread " << id << " entering critical section.\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟工作
std::cout << "Thread " << id << " leaving critical section.\n";
lock.unlock();
}
int main() {
std::thread t1(critical_section, 1);
std::thread t2(critical_section, 2);
t1.join();
t2.join();
return 0;
}
自旋锁的实现稍显晦涩,有些概念需要注意一下:
--1 !flag_.exchange(true, std::memory_order_acquire:exchange 是 C++ 标准库中 std::atomic 类型提供的一个原子操作方法。它的作用是 原子地交换 当前变量的值与指定值,并返回原先的值。如果原先是False 交换之后返回False,取反后While就会退出,代表上锁了。下一个线程进来时发现flag_是True了,只能进入循环,并通过spin_count限制循环次数。
--2 std::this_thread::yield():std::this_thread::yield() 是 C++11 标准库中的一个函数,用于使当前执行的线程主动放弃 CPU 时间片,并允许其他线程有机会执行。当前线程会被放回线程调度队列中,等待操作系统再次选择它来执行。
--3 lock 本身就是竞争资源为什么不需要锁呢? 因为lock的成员有两个,一个是原子量flag_,另外一个只是一个参数量,没有线程会进行写入操作,不影响线程安全。
--4 spin_count 会有线程安全的问题吗?显然不会,因为这是一个临时变量,每个线程有自己的副本,存放在独立的栈空间。