在C++中通过函数实现并发编程是提升程序性能的常见手段,标准库提供了std::thread、互斥锁、条件变量等工具支持并发逻辑开发,但实际使用过程中存在不少容易出错的场景。

常见陷阱分类与解析
1. 数据竞争问题
当多个线程同时访问同一个共享变量,且至少有一个线程执行写操作时,就会产生数据竞争,这是并发编程中最常见的陷阱。很多开发者会忽略函数内捕获的外部变量的共享属性,导致多个线程同时修改同一块内存。
比如下面的错误示例,多个线程同时修改同一个计数器变量:
#include <iostream>
#include <thread>
#include <vector>
// 错误示例:存在数据竞争
void increment(int* counter) {
for (int i = 0; i < 1000; ++i) {
++(*counter); // 多个线程同时修改counter指向的内存,无同步措施
}
}
int main() {
int counter = 0;
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(increment, &counter);
}
for (auto& t : threads) {
t.join();
}
std::cout << "最终计数器值:" << counter << std::endl; // 结果大概率小于5000
return 0;
}
规避方案是使用互斥锁对共享变量的访问进行保护,修改后的正确示例如下:
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
std::mutex mtx; // 定义互斥锁保护共享变量
void increment(int* counter) {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 加锁,作用域结束自动解锁
++(*counter);
}
}
int main() {
int counter = 0;
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(increment, &counter);
}
for (auto& t : threads) {
t.join();
}
std::cout << "最终计数器值:" << counter << std::endl; // 结果稳定为5000
return 0;
}
2. 函数对象生命周期管理不当
当线程绑定的函数对象或者捕获的变量生命周期短于线程执行周期时,就会出现悬空引用或者悬空指针问题,导致程序未定义行为。
比如下面的错误示例,线程绑定了局部变量的引用:
#include <iostream>
#include <thread>
#include <string>
void print_msg(const std::string& msg) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
std::cout << msg << std::endl;
}
int main() {
std::thread t;
{
std::string local_msg = "局部变量消息";
t = std::thread(print_msg, std::ref(local_msg)); // 传递局部变量的引用
} // local_msg在这里被销毁
t.join(); // 线程执行时访问已经销毁的local_msg,行为未定义
return 0;
}
规避方案是避免传递短生命周期对象的引用或指针,优先传递值拷贝,或者保证对象的生命周期长于线程执行周期:
#include <iostream>
#include <thread>
#include <string>
void print_msg(std::string msg) { // 参数改为值传递,拷贝一份数据
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << msg << std::endl;
}
int main() {
std::thread t;
{
std::string local_msg = "局部变量消息";
t = std::thread(print_msg, local_msg); // 传递拷贝,不受局部变量生命周期影响
}
t.join();
return 0;
}
3. 死锁问题
死锁通常发生在多个线程互相持有对方需要的锁,并且都在等待对方释放锁的场景,常见原因是加锁顺序不一致。
错误示例如下,两个线程分别以不同顺序加锁:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1;
std::mutex mtx2;
// 线程1的加锁顺序:先mtx1后mtx2
void thread_func1() {
std::lock_guard<std::mutex> lock1(mtx1);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock2(mtx2);
std::cout << "线程1执行完成" << std::endl;
}
// 线程2的加锁顺序:先mtx2后mtx1
void thread_func2() {
std::lock_guard<std::mutex> lock2(mtx2);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock1(mtx1);
std::cout << "线程2执行完成" << std::endl;
}
int main() {
std::thread t1(thread_func1);
std::thread t2(thread_func2);
t1.join();
t2.join();
return 0;
}
规避方案是统一所有线程的加锁顺序,或者使用std::lock同时锁住多个互斥锁,避免死锁:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1;
std::mutex mtx2;
void thread_func1() {
// 同时锁住两个互斥锁,避免死锁
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock); // 接管已加锁的mtx1
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock); // 接管已加锁的mtx2
std::cout << "线程1执行完成" << std::endl;
}
void thread_func2() {
// 和线程1相同的加锁逻辑,顺序一致
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "线程2执行完成" << std::endl;
}
int main() {
std::thread t1(thread_func1);
std::thread t2(thread_func2);
t1.join();
t2.join();
return 0;
}
4. 条件变量使用不当
使用条件变量时如果没有配合互斥锁,或者等待条件时没有使用循环检查,容易出现虚假唤醒或者数据不一致的问题。
错误示例如下,等待条件时没有循环检查:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void wait_for_ready() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock); // 没有循环检查ready,虚假唤醒后会继续执行
std::cout << "准备完成,开始执行" << std::endl;
}
void set_ready() {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::unique_lock<std::mutex> lock(mtx);
ready = true;
cv.notify_all();
}
int main() {
std::thread t1(wait_for_ready);
std::thread t2(wait_for_ready);
std::thread t3(set_ready);
t1.join();
t2.join();
t3.join();
return 0;
}
规避方案是在wait调用外使用循环检查条件,确保即使出现虚假唤醒也不会错误执行逻辑:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void wait_for_ready() {
std::unique_lock<std::mutex> lock(mtx);
// 循环检查条件,避免虚假唤醒
while (!ready) {
cv.wait(lock);
}
std::cout << "准备完成,开始执行" << std::endl;
}
void set_ready() {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::unique_lock<std::mutex> lock(mtx);
ready = true;
cv.notify_all();
}
int main() {
std::thread t1(wait_for_ready);
std::thread t2(wait_for_ready);
std::thread t3(set_ready);
t1.join();
t2.join();
t3.join();
return 0;
}
总结
使用C++函数进行并发编程时,需要重点关注共享数据的访问控制、函数对象生命周期、锁的使用顺序、条件变量的等待逻辑这几个方面。开发过程中可以优先使用标准库提供的RAII类管理锁资源,避免手动加锁解锁的遗漏,同时尽量减少共享可变状态的使用,从根源上降低并发问题的出现概率。如果出现并发相关的异常问题,可以优先排查上述几类常见陷阱,快速定位问题原因。
C++并发编程std_thread互斥锁条件变量数据竞争修改时间:2026-07-01 21:00:45