多线程之线程创建与管理:面试官问“机器人系统为什么必须用多线程” 从今天开始进入多线程板块。这块内容在C面试里分量很重——互斥锁、死锁、条件变量、原子操作、线程池一连串的话题每个都能聊一篇。先从最基础的开始线程的创建与管理。为什么要用多线程这个问题在机器人开发里答案特别明确因为你的系统有多个独立的任务必须同时运行。想象一下一台自主导航的机器人需要同时做这些事读取激光雷达数据10Hz、读取IMU数据200Hz、运行SLAM算法5Hz、执行路径规划2Hz、发送电机控制指令50Hz。这些任务的频率不一样逻辑完全独立但必须同时跑。如果你用单线程串行执行一个任务卡住了其他任务全停——机器人就会在原地发呆然后撞上墙。C11线程的基本用法C11之前写线程得用平台相关的API——Linux用pthreadWindows用CreateThread。C11统一了接口用std::thread就行了。#include thread #include iostream void lidarTask() { while (true) { auto scan readLidar(); processScan(scan); this_thread::sleep_for(100ms); // 10Hz } } void imuTask() { while (true) { auto data readIMU(); updateOdometry(data); this_thread::sleep_for(5ms); // 200Hz } } int main() { thread t1(lidarTask); thread t2(imuTask); t1.join(); // 等待线程结束 t2.join(); return 0; }几个要注意的点thread对象创建之后线程就已经开始运行了。你必须选择join等待线程结束或者detach让线程独立运行。如果你什么都不做就销毁thread对象程序会直接abort——这是C11的硬性规定。void example() { thread t(someTask); // 如果这里抛异常t被销毁时没有join或detach程序崩溃 } // 安全的做法用RAII包装 class ScopedThread { thread t_; public: explicit ScopedThread(thread t) : t_(std::move(t)) {} ~ScopedThread() { if (t_.joinable()) t_.join(); } ScopedThread(const ScopedThread) delete; ScopedThread operator(const ScopedThread) delete; }; void safeExample() { ScopedThread t(thread(someTask)); // 即使抛异常析构函数也会join }给线程传参数thread的构造函数可以接受参数传给线程函数void sensorTask(const string sensorName, int frequency) { cout sensorName running at frequency Hz endl; // ... } thread t1(sensorTask, lidar, 10); thread t2(sensorTask, imu, 200);这里有个坑参数默认是按值拷贝传递的。如果你想传引用必须用std::refvoid processData(vectorfloat data) { // 修改data } vectorfloat sensorData; // thread t(processData, sensorData); // 错这是拷贝 thread t(processData, std::ref(sensorData)); // 对传引用机器人系统中的线程架构实际项目里线程的组织方式通常有两种。第一种是每任务一线程每个独立的任务有自己的线程。简单直接但线程多了之后管理困难。class RobotSystem { thread lidarThread_, imuThread_, plannerThread_, controllerThread_; public: void start() { lidarThread_ thread(RobotSystem::lidarLoop, this); imuThread_ thread(RobotSystem::imuLoop, this); plannerThread_ thread(RobotSystem::plannerLoop, this); controllerThread_ thread(RobotSystem::controllerLoop, this); } void stop() { running_ false; lidarThread_.join(); imuThread_.join(); plannerThread_.join(); controllerThread_.join(); } };第二种是线程池创建固定数量的线程把任务丢到队列里让线程池调度。下篇会详细讲这个。ROS2用的是第一种方式的变体——每个回调函数在自己的线程里执行。ROS2的executor负责管理这些线程把回调分发到对应的线程上。线程的生命周期管理创建线程容易管理难。几个实际问题怎么让线程停下来不能直接杀死一个线程C没有这个功能。正确做法是设一个标志位线程循环里检查这个标志atomicbool running_{true}; void sensorLoop() { while (running_) { auto data readSensor(); processData(data); } // 清理资源安全退出 } void stop() { running_ false; thread_.join(); // 等待线程退出 }怎么知道线程有没有正常启动可以在线程函数开头设一个ready标志主线程等待这个标志void workerLoop() { initResources(); ready_ true; // 通知主线程我准备好了 while (running_) { // 工作循环 } }面试中的关键考点thread和process有什么区别线程共享同一进程的内存空间进程有独立的地址空间。线程创建和切换的开销比进程小得多。但线程之间共享数据需要同步进程之间不需要但也意味着隔离性更好。join和detach有什么区别join会阻塞调用者直到目标线程执行完毕。detach让线程独立运行之后你没法再等它了。detach之后线程的资源在线程结束时由系统自动回收。一个进程最多能创建多少线程取决于系统资源。每个线程需要一定的栈空间Linux默认8MB所以理论上受限于虚拟地址空间。实际上创建几百到几千个线程是常见的。但线程太多会导致频繁的上下文切换反而降低性能。实战中还有一个很容易被忽视但非常实用的技巧给线程命名。在Linux下可以用pthread_setname_np给线程设置名字然后在top -H或者/proc/self/task/里就能看到每个线程的名字。在机器人系统里线程一多之后传感器采集线程、规划线程、控制线程、通信线程……用GDB调试时如果所有线程都显示为无名线程排查起来非常痛苦。给每个线程起个有意义的名字调试效率会大幅提升。面试时如果你能提到这种工程实践中的小技巧面试官会觉得你是一个有实战经验的开发者而不只是在课本上学的理论。给正在准备面试的你多线程的基础——创建线程、传参数、join/detach——这些是必须会写的。面试的时候面试官可能让你手写一个简单的多线程程序比如一个生产者消费者模型。但光会创建线程不够。线程之间的同步才是多线程的核心问题——数据竞争、死锁、条件等待。这些是后面几篇的内容。下篇聊互斥锁与死锁——一个真实死锁bug的排查过程。这是多线程编程里最容易踩的坑面试几乎必考。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第53篇 零拷贝技术——大数据量传输的性能关键 下一篇预告第55篇 多线程之互斥锁与死锁——一个真实死锁bug的排查过程有任何问题欢迎评论区留言我会尽量回复。