核心内容摘要
学习笔记——软件工程
我们可以把“多线程编程 (Multi-threaded Programming)”理解为“并发管理”。
核心比喻厨房与厨师进程 (Process)一家餐厅厨房。
它有独立的冰箱内存、独立的煤气管道资源。
餐厅倒闭了进程崩溃里面的东西都没了。
线程 (Thread)厨房里的厨师。
单线程厨房里只有一个厨师。
他必须先切菜切完再炒菜炒完再装盘。
客人等得花儿都谢了。
多线程厨房里有三个厨师。
厨师 A 负责切菜。
厨师 B 负责炒菜。
厨师 C 负责装盘。
优势大家同时干活效率极高利用多核 CPU。
风险他们共享同一个冰箱内存/PlanContext。
如果厨师 A 正在拿肉厨师 B 突然把冰箱门关了夹住 A 的手或者两人同时抢一把刀就会出事。
为什么要搞多线程自动驾驶场景在你的PlanningNode里如果只有单线程代码是这样跑的等激光雷达数据进来耗时 10ms...等定位数据进来耗时 5ms...开始规划路径耗时 50ms...发送控制指令。
后果车子在等数据的时候是“发呆”的规划频率极其低下车子开起来一顿一顿的甚至会因为反应慢撞墙。
引入多线程后线程 1收发员专门负责收 ROS 消息Callback。
有数据来就塞进PlanContext不用等规划算完。
线程 2计算员专门负责算 OSQPTimer Loop。
不管有没有新数据我每 100ms 必须算一次保证车子一直有指令。
这就是“异步高效”的本质。
多线程编程的三个核心要素在 C 代码中你主要会遇到这三个东西A. 创建线程 (Create)#include thread void task() { // 具体的干活逻辑 } int main() { std::thread t1(task); // 创建并启动线程 t1 t
detach(); // 让它自己去跑不管它了 // 或者 t
join(); // 主线程在这里等直到 t1 干完活才继续 }B. 资源共享 (Sharing)就像大家共用一个冰箱。
在你的项目中PlanContext就是那个最大的共享资源。
ROS 回调线程往里面写。
规划主线程从里面读。
C. 同步与互斥 (Synchronization Mutex)这是最难的地方。
因为大家共用资源所以必须立规矩。
竞态条件 (Race Condition)两个线程同时改一个变量导致结果错误。
就是刚才说的“抢刀”。
互斥锁 (Mutex)解决竞态条件的工具。
刚才讲的“试衣间锁”。
死锁 (Deadlock)厨师 A 拿着刀等锅。
厨师 B 拿着锅等刀。
两人僵持不下厨房彻底停摆。
你的代码架构中的体现回头看你的架构图多线程是这样运作的ROS 2 的底层ROS 2 默认就是一个多线程的中间件。
当你定义了多个Subscription订阅时ROS 内部会有线程池Executor帮你去“监听”数据。
回调并发当LocalizationPose定位和ObstacleArray感知同时到达时可能会有两个不同的线程同时试图调用PlanContext::updateData。
保护机制所以PlanContext里必须有std::mutex。
谁先抢到锁谁先写另一个排队。
提醒敬畏全局变量只要你定义了一个全局变量或者静态变量单例就要立刻想到“这会被多个线程同时访问吗”加锁要用 RAII永远使用std::lock_guard或std::unique_lock不要手动lock()和unlock()防止死锁。
多看 Log多线程的 Bug 很难复现因为是概率性的。
如果程序偶尔崩溃大概率是哪里没加锁。