在C++多线程编程场景中,当多个线程同时访问并修改同一个共享变量时,由于线程调度的不确定性,很容易出现数据竞争问题,导致共享数据的修改结果不可预期。std::mutex是C++11标准引入的互斥锁类型,通过保证同一时间只有一个线程能持有锁并访问共享资源,从根源上避免数据竞争的发生。

数据竞争的产生原因
数据竞争通常发生在以下场景:多个线程同时读写同一个共享变量,且至少有一个线程执行写操作,同时没有使用任何同步机制。比如两个线程同时对一个全局计数器执行加1操作,实际执行时可能出现两个线程都读取到相同的初始值,各自加1后写回,最终计数器只增加了1而不是2的情况。
std::mutex基本用法
std::mutex的核心逻辑是:线程访问共享资源前先调用lock()方法获取锁,如果锁已经被其他线程持有,当前线程会阻塞等待;访问完成后调用unlock()方法释放锁,让其他等待的线程可以获取锁。
下面是一个简单的计数器示例,展示未加锁和加锁的区别:
#include <iostream>
#include <thread>
#include <mutex>
// 共享计数器
int counter = 0;
// 定义互斥锁
std::mutex mtx;
// 未加锁的计数函数,会出现数据竞争
void unsafe_increment(int times) {
for (int i = 0; i < times; ++i) {
// 多个线程同时读写counter,会出现问题
int temp = counter;
temp += 1;
counter = temp;
}
}
// 加锁的计数函数,避免数据竞争
void safe_increment(int times) {
for (int i = 0; i < times; ++i) {
// 获取锁
mtx.lock();
int temp = counter;
temp += 1;
counter = temp;
// 释放锁
mtx.unlock();
}
}
int main() {
std::thread t1(unsafe_increment, 10000);
std::thread t2(unsafe_increment, 10000);
t1.join();
t2.join();
std::cout << "未加锁的计数器结果: " << counter << std::endl; // 结果通常小于20000
counter = 0;
std::thread t3(safe_increment, 10000);
std::thread t4(safe_increment, 10000);
t3.join();
t4.join();
std::cout << "加锁的计数器结果: " << counter << std::endl; // 结果稳定为20000
return 0;
}
配套RAII工具简化锁管理
直接使用lock()和unlock()容易出现忘记释放锁的问题,比如函数提前返回或者抛出异常时,锁没有被释放会导致死锁。C++标准库提供了两个RAII类型的锁管理工具,自动在构造时获取锁,析构时释放锁,避免手动管理的问题。
std::lock_guard
std::lock_guard是最常用的轻量级锁管理工具,功能简单,不支持手动释放锁,生命周期结束后自动释放。适合作用域明确的加锁场景。
#include <iostream>
#include <thread>
#include <mutex>
int shared_data = 0;
std::mutex data_mtx;
void update_data(int value) {
// 构造时自动获取锁
std::lock_guard<std::mutex> lock(data_mtx);
shared_data += value;
// 函数结束时lock析构,自动释放锁
// 即使函数抛出异常,也会保证锁被释放
}
int main() {
std::thread t1(update_data, 10);
std::thread t2(update_data, 20);
t1.join();
t2.join();
std::cout << "共享数据结果: " << shared_data << std::endl;
return 0;
}
std::unique_lock
std::unique_lock比lock_guard更灵活,支持延迟加锁、手动释放锁、转移锁的所有权等操作,还可以配合std::condition_variable使用。适合需要更精细控制锁的场景。
#include <iostream>
#include <thread>
#include <mutex>
int shared_value = 0;
std::mutex value_mtx;
void process_value() {
std::unique_lock<std::mutex> lock(value_mtx, std::defer_lock); // 延迟加锁,不立即获取锁
// 做一些不需要锁的准备工作
// 需要访问共享资源时再获取锁
lock.lock();
shared_value += 5;
// 提前释放锁,不需要等到作用域结束
lock.unlock();
// 后续不需要锁的操作
}
int main() {
std::thread t1(process_value);
std::thread t2(process_value);
t1.join();
t2.join();
std::cout << "处理后的值: " << shared_value << std::endl;
return 0;
}
使用std::mutex的注意事项
- 避免死锁:如果多个线程需要获取多个锁,要保证所有线程获取锁的顺序一致,或者使用std::lock同时获取多个锁,避免互相等待的情况。
- 锁的粒度要合适:不要长时间持有锁,只把需要访问共享资源的代码放在加锁范围内,减少其他线程的等待时间。
- 不要重复加锁:同一个线程对同一个std::mutex连续调用lock()会导致死锁,递归场景可以使用std::recursive_mutex。
- 优先使用RAII工具:尽量使用lock_guard或者unique_lock管理锁,不要手动调用lock()和unlock(),减少出错概率。
其他避免数据竞争的方式
除了使用std::mutex加锁,C++还提供了其他线程安全的工具,比如std::atomic原子类型,适合简单的变量读写场景,性能比互斥锁更好;还有std::shared_mutex读写锁,适合读多写少的场景,多个读线程可以同时持有读锁,写线程需要独占写锁。
开发者可以根据实际的业务场景选择合适的同步方式,在保证线程安全的前提下尽量提升程序的运行效率。
std::mutexC++多线程数据竞争线程同步锁机制修改时间:2026-07-20 15:09:33