C++中多线程和互斥锁的基本使用
- C++中多线程的基本使用
-
- [一、 使用普通函数创建线程](#一、 使用普通函数创建线程)
- [二、 使用 Lambda 表达式创建线程(推荐)](#二、 使用 Lambda 表达式创建线程(推荐))
- [三、 典型语法案例汇总](#三、 典型语法案例汇总)
-
- [1. 捕获引用 + 修改主线程变量](#1. 捕获引用 + 修改主线程变量)
- [2. detach 创建"后台线程"](#2. detach 创建“后台线程”)
- [3. 使用类成员函数创建线程](#3. 使用类成员函数创建线程)
- [4. 多线程 + 互斥锁保护共享资源](#4. 多线程 + 互斥锁保护共享资源)
- [四、 总结](#四、 总结)
- C++中互斥锁的基本使用
-
- 一、互斥锁的作用
- [二、未使用互斥锁 vs 使用互斥锁](#二、未使用互斥锁 vs 使用互斥锁)
- 三、总结对比
- 四、数据竞争分析与解决:
-
- [1. 未使用互斥锁时的错误表现:](#1. 未使用互斥锁时的错误表现:)
- [2. 问题原因分析](#2. 问题原因分析)
- [3. 互斥锁同步](#3. 互斥锁同步)
参考:
C++多线程学习详解
C++多线程详解(全网最全)
带你吃透C++互斥锁
C++中多线程的基本使用
一、 使用普通函数创建线程
cpp
#include <iostream>
#include <thread>
void doWork(int x) {
std::cout << "Working: " << x << std::endl;
}
int main() {
std::thread t(doWork, 42); // 传入函数指针和参数
t.join(); // 等待线程完成
return 0;
}
特点:
- 简单直观,适合逻辑独立的函数;
- 无法直接访问主线程局部变量(除非使用全局或传参);
二、 使用 Lambda 表达式创建线程(推荐)
cpp
#include <iostream>
#include <thread>
int main() {
int value = 10;
std::thread t([value]() {
std::cout << "Lambda thread running: " << value << std::endl;
});
t.join();
return 0;
}
特点:
- 可以捕获外部变量(按值或按引用);
- 灵活、简洁,适合小函数或带状态逻辑;
- 推荐用于现代 C++ 多线程编程;
变量捕获方式说明
cpp
[value]() { ... } // 值捕获(只读)
[&value]() { ... } // 引用捕获(可读写)
[=]() { ... } // 捕获当前作用域所有变量的副本
[&]() { ... } // 捕获所有变量的引用
[this]() { ... } // 捕获 this 指针(常用于类内部)
三、 典型语法案例汇总
1. 捕获引用 + 修改主线程变量
cpp
#include <iostream>
#include <thread>
int main() {
int result = 0;
std::thread t([&result]() {
result = 100;
});
t.join();
std::cout << "Result = " << result << std::endl;
return 0;
}
2. detach 创建"后台线程"
cpp
#include <iostream>
#include <thread>
#include <chrono>
int main() {
std::thread t([]() {
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Background thread finished!" << std::endl;
});
t.detach(); // 不阻塞主线程
std::cout << "Main thread exits quickly" << std::endl;
return 0;
}
⚠ 使用
detach()
时要保证线程的生命周期和资源访问安全,防止访问已经被释放的变量。
3. 使用类成员函数创建线程
cpp
#include <iostream>
#include <thread>
class Worker {
public:
void run(int x) {
std::cout << "Worker running: " << x << std::endl;
}
};
int main() {
Worker w;
std::thread t(&Worker::run, &w, 123); // 对象地址 + 参数
t.join();
return 0;
}
4. 多线程 + 互斥锁保护共享资源
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void printSafe(int id) {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁/解锁
std::cout << "Thread ID: " << id << std::endl;
}
int main() {
std::thread t1(printSafe, 1);
std::thread t2(printSafe, 2);
t1.join();
t2.join();
return 0;
}
四、 总结
方法 | 使用场景 | 优点 | 缺点 |
---|---|---|---|
普通函数 + std::thread |
逻辑封装好、参数固定的线程任务 | 简洁明了 | 无法捕获外部变量 |
Lambda + std::thread |
动态逻辑、需要捕获状态的任务 | 灵活、可访问外部变量、现代推荐 | 不适合太长逻辑 |
C++中互斥锁的基本使用
一、互斥锁的作用
用于在多线程程序中保护共享资源 ,防止数据竞争(data race)。
常见用法:
cpp
#include <mutex>
std::mutex mtx;
void threadFunc() {
mtx.lock(); // 加锁
// 临界区代码(访问共享资源)
mtx.unlock(); // 解锁
}
更推荐的方式:使用 std::lock_guard
cpp
#include <mutex>
std::mutex mtx;
void threadFunc() {
std::lock_guard<std::mutex> lock(mtx); // 析构自动释放锁
// 临界区代码
}
二、未使用互斥锁 vs 使用互斥锁
【未使用互斥锁的版本】(存在数据竞争)
cpp
#include <iostream>
#include <thread>
int counter = 0;
void add() {
for (int i = 0; i < 100000; ++i) {
counter++; // 多线程同时修改,可能会出现数据竞争
}
}
int main() {
std::thread t1(add);
std::thread t2(add);
t1.join();
t2.join();
std::cout << "Final counter (no mutex): " << counter << std::endl;
return 0;
}
【使用互斥锁的版本】(避免数据竞争)
cpp
#include <iostream>
#include <thread>
#include <mutex>
int counter = 0;
std::mutex mtx;
void add() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
counter++;
}
}
int main() {
std::thread t1(add);
std::thread t2(add);
t1.join();
t2.join();
std::cout << "Final counter (with mutex): " << counter << std::endl;
return 0;
}
三、总结对比
项目 | 未使用互斥锁 | 使用互斥锁(std::mutex ) |
---|---|---|
线程安全 | 否,可能发生数据竞争 | 是,线程安全 |
运行结果 | 不确定,可能小于理论值 | 稳定,符合预期值 |
性能 | 较快(无锁) | 稍慢(加锁释放有开销) |
推荐使用场景 | 多线程只读,无共享写 | 有共享写访问时必须使用 |
四、数据竞争分析与解决:
1. 未使用互斥锁时的错误表现:
日志输出异常:
[ INFO] [1754040346.741811266]: has returned trajectory with 18446744059935218265 points
[ INFO] [1754040346.741849866]: Waypoint 0: pos(0.00, 0.00, 0.00), yaw=0.00
[ INFO] [1754040346.741897766]: Waypoint 1: pos(0.00, 0.00, 0.00), yaw=0.00
...
[ INFO] [1754040346.752095449]: Waypoint 198: pos(0.00, 1385839276081958023820612523912152498000590369501888262879377677457232831957974476045551661306264153665361036034313791385840336402935561101059297341547615728513508795440121964374800002716729465953814831774142238856052736.00, 93166380607490246256839516428781872302764913110062132309905673477052295654788485977494611625942385485525609127354754662271381274992469689153543502627653304785958304676908077889204905930400618944397312.00), yaw=1012484461684370672638642220875319969585287415698854255336934621478697955025548036753087232028352492273457411087994188586700792
飞行行为异常:
-
轨迹点数据出现极其巨大的无意义数值
-
系统不稳定,可能出现崩溃
2. 问题原因分析
-
写操作:后台线程在Decision()中更新latest_predict_state_
-
读操作:主线程在StateGet()中读取latest_predict_state_
-
无同步机制:两个线程同时访问同一块内存,导致数据损坏
当两个线程同时访问时:
-
一个线程正在写入vector的大小信息
-
另一个线程同时读取,可能读取到部分写入的数据
-
导致vector的size字段被破坏,显示为SIZE_MAX
3. 互斥锁同步
修改前(无保护):
cpp
// 头文件
class Search {
private:
std::vector<State> latest_predict_state_;
};
// 写入操作
void Search::Decision(...) {
latest_predict_state_ = execute(); // 直接写入,无保护
}
// 读取操作
std::vector<State> Search::StateGet() {
return latest_predict_state_; // 直接读取,无保护
}
修改后(有保护):
cpp
// 头文件
#include <mutex>
class Search {
private:
std::vector<State> latest_predict_state_;
mutable std::mutex latest_predict_state_mutex_; // 添加互斥锁
};
// 写入操作
void Search::Decision(...) {
std::vector<State> new_trajectory = execute();
// 在锁保护下更新共享数据
{
std::lock_guard<std::mutex> lock(latest_predict_state_mutex_);
latest_predict_state_ = new_trajectory;
}
}
// 读取操作
std::vector<PredictState> Search::StateGet() {
std::lock_guard<std::mutex> lock(latest_predict_state_mutex_);
return latest_predict_state_; // 在锁保护下读取
}