std::mutex和
std::atomic
std::mutex
保护一个代码块 ,而 std::atomic
保护对单个变量的操作
std::mutex
std::mutex
(互斥锁) 的作用是保护一个"临界区 (Critical Section)",也就是一段必须以原子方式执行的代码块。它确保在任何时刻,只有一个线程可以进入这段代码并执行其中的操作
- 锁定 (Locking) :一个线程在进入临界区前必须先获得锁 (
lock
)。如果锁已被其他线程持有,该线程将被阻塞 (Block),即进入休眠状态,直到锁被释放 - 高开销:线程阻塞和唤醒需要操作系统介入,涉及上下文切换,开销相对较大
- 保护复杂操作:非常适合保护需要多个步骤才能完成的操作,或者需要同时访问多个变量的场景
转账操作包含"A账户减钱"和"B账户加钱"两个步骤,这两个步骤必须作为一个整体完成,不可分割:
c++
#include <mutex>
class BankAccount {
private:
std::mutex mtx_;
double balance_ = 0;
public:
void deposit(double amount) {
std::lock_guard<std::mutex> lock(mtx_); // 自动加锁
balance_ += amount;
} // lock_guard析构时自动解锁
void withdraw(double amount) {
std::lock_guard<std::mutex> lock(mtx_);
if (balance_ >= amount) {
balance_ -= amount;
}
}
};
std::mutex
保护的是 balance_
的整个读-改-写过程,确保其不被其他线程中断
std::atomic
std::atomic
(原子类型) 的作用是为单个变量的读、写、修改 等操作提供原子性保证。它通常用于实现无锁 (Lock-free) 编程
- 原子指令 :它通常利用CPU提供的特殊原子指令(如x86上的
LOCK ADD
)来完成操作。这意味着操作是不可中断的,一步到位 - 无阻塞:线程在执行原子操作时不会被操作系统挂起。如果多个线程同时尝试,它们会由CPU硬件来仲裁,但不会发生上下文切换
- 低开销:因为不涉及操作系统层面的锁定和线程调度,其性能远高于互斥锁
eg:多线程计数器
c++
#include <atomic>
#include <thread>
#include <vector>
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i < 10000; ++i) {
counter++; // 这是一个原子的读-改-写操作
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
// counter 的最终结果会是 100000
}
counter++
这一行代码是原子性的,不会出现数据竞争
特性 | std::mutex | std::atomic |
---|---|---|
保护对象 | 代码块(一系列操作) | 单个变量 |
机制 | 阻塞式锁定 | 通常为无锁原子指令 |
性能开销 | 较高 | 非常低 |
适用场景 | 保护复杂逻辑、多变量状态 | 简单的状态标记、计数器、指针交换等 |