ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从零设计一个运动控制软件:Actor 线程模型为什么是设备行业的主流

从零设计一个运动控制软件:Actor 线程模型为什么是设备行业的主流 本文站在设备行业架构视角聊聊「从零设计一台多工位运动控制设备的软件」时为什么会自然走到 Actor 线程模型这条路。不绑定具体框架适合做半导体 Handler、贴装、检测、分拣、包装等自动化设备软件的工程师和面试参考。一、先看清设备软件要解决的本质问题很多人一上来就纠结「用什么框架」「要不要上 ROS」「要不要用 PLC 上位机」。其实先退一步看清楚设备软件到底要解决什么。一台典型的自动化设备Handler、贴片机、分选机、测试分拣线通常有这几个特征多工位并行上料、传输、测试、下料同时进行不是一条直线走到底。强硬件依赖电机、气缸、真空、传感器、相机、测试机时序错了一步就撞机或废料。长周期流程一个料从进到出可能几十秒到几分钟中间要等测试、等真空、等下一工位就绪。安全优先任何一处异常都要能快速停机、不让料继续往下走。可维护可扩展今天 2 条料道明天加到 4 条今天测一种产品明天换型。这五条决定了设备软件不是「Web 后端那种请求-响应」模型也不是「桌面软件那种事件驱动 UI」模型而是一种多执行单元 各自状态机 资源互斥 异常广播停机的模型。二、从零设计时你会撞上的三道坎如果你真的从零写一台设备软件大概率会经历这三个阶段。第一道坎单线程写不下去最朴素的写法是一个大循环while (running) { 上料(); 搬运1(); 测试(); 搬运2(); 下料(); }很快发现测试要 10 秒搬运 1 只能干等产能上不去。于是你想让搬运和测试并行。第二道坎多线程 共享变量越写越乱开了几个线程用一堆bool互相通知bool bufferReady false;bool armBusy false;// 线程 A 改 bufferReady线程 B 轮询这种代码写到第 5 个模块就开始失控谁改了谁的状态搞不清一个 bool 既表示「就绪」又表示「完成」语义混乱加新模块要改一堆老代码死锁、竞态、漏停机层出不穷。笔者就是吃过这种亏后边才开始反思有没有更方便的写法第三道坎你开始抽象「执行单元」痛够了你会意识到每个模块上料站、搬运手、测试台、下料站其实有共同的抽象有自己的生命周期启动、运行、暂停、停止、回零。有自己的状态/步骤取料中、放料中、等待中、故障。需要和其它模块协作等对方就绪、通知对方完成。出问题要能统一停机。一旦你把这个抽象提炼出来给它起个名字——Actor——你就走到了设备行业的主流线程模型。三、Actor 在设备软件里到底是什么注意这里说的 Actor 不是 Akka/ Erlang 那种「信箱消息传递」的 Actor 模型。设备行业说的 Actor 更朴素一个独立执行单元 一个长期线程 一个状态机 一组对外协作接口。它有这几个关键属性。1. 一个 Actor 一个线程每个 Actor上料站、搬运手、测试台……独占一个线程线程里跑它的WorkFlow()主循环。线程生命周期和 Actor 对象绑定不是「取一个任务做完就退出」的短任务。2. 状态机驱动Actor 的WorkFlow不是一个线性函数而是while (running) { wait_if_not_started(); if (faulted) handle_fault(); switch (step) { case 取料: ...; step 搬运; break; case 搬运: ...; step 放料; break; case 放料: ...; step 完成; break; } }每个 Actor 自己推进自己的步骤不依赖外部调度。3. 统一的生命周期接口所有 Actor 都能被统一地启动、停止、暂停、回零、结批。这样总控可以一键StopAll()、RunAll()、HomeAll()不用关心每个模块内部细节。4. 资源占用与互斥工位、Buffer、压台这些共享资源Actor 之间通过「占用/释放」协议互斥而不是裸 bool 轮询。5. 异常可广播停机任意 Actor 发现严重故障调用一个全局入口比如ControllerStop()总控停掉所有 Actor避免残料继续流动。四、Actor 模型解决了第二道坎里的哪些问题回到前面「多线程 共享 bool」的混乱Actor 模型对症下药之前的问题Actor 模型的解法状态散落在全局 bool状态封装在 Actor 内部对外只暴露接口模块间互相直接改对方变量通过占用协议、就绪握手、事件通知协作加新模块要改老代码新 Actor 注册进注册表老 Actor 不动停机逻辑各写各的统一StopAll一个入口停全部步骤推进混乱每个 Actor 自己的状态机边界清晰本质上Actor 把「谁的状态、谁推进、谁负责停」这三件事绑在了一起消除了「状态归属不清」这个最大的混乱源。五、Actor 之间怎么协作三种典型手段设备软件里 Actor 不是孤岛它们要配合完成「料从进到出」。常见的协作手段有三种理解这三种就够应付大多数场景。手段一资源占用协议互斥最典型的场景两个手臂抢同一个 Buffer。做法是给共享资源加一个「占用者」字段bool Station::SetUsedBy(Actor* a) { lock(); if (m_owner nullptr || m_owner a) { m_owner a; return true; } return false; }Actor 想用资源先SetUsedBy(this)用完SetNotUsedBy(this)。抢不到就等下一拍。这比裸 bool 强在占用关系明确、可查询、可释放、可防止重入。手段二就绪握手同步A 要把料给 B得等 B 说「我能接」。这种「准备好」握手通常有两种实现轮询式A 查B.IsReadyToRecv()B 改自己的标志。简单但有延迟、易竞态。事件/条件变量式B 准备好后Post一个带数据的事件A 阻塞Wait。无空转、能带数据、更解耦。工业里两种都有前者老项目多后者新项目更推荐。手段三异常广播停机任意 Actor 发现严重故障走统一上报路径Actor::Alarm(error); Actor::ControllerStop(); // 全局入口停整机总控收到后停掉所有 Actor避免「A 挂了 B 还在往里送料」的二次事故。六、为什么不用线程池线程池和 Actor 模型看起来都是「多线程」但适用场景完全不同。维度线程池Actor 一对象一线程任务粒度短任务做完还线程长期状态机常驻状态归属任务无状态或状态外置状态在 Actor 内调度框架调度任务排队每个 Actor 自驱适合场景Web 请求、图像处理、IO 密集硬件流程、状态机、实时控制取消任务级取消Actor 级停止标志设备软件里每个模块是长期运行的状态机不是「处理完就结束」的短任务。把状态机塞进线程池反而要额外管理「哪个线程现在跑哪个 Actor 的哪一步」复杂度更高。所以设备行业几乎清一色用「一 Actor 一线程」而不是线程池。这不是落后是场景决定的。七、Actor 模型的设计要点可复用清单如果让你主导一台新设备的软件架构按 Actor 模型落地时抓住这几条1. 抽象一个公共基类所有执行单元继承同一个基类提供统一接口class Actor { void Run(); void Stop(); void Home(); void EndLot(); bool IsRun(); bool IsFaulted(); virtual void WorkFlow() 0; // 子类实现自己的状态机 };2. 用注册表管理所有 Actorstatic vectorActor* g_Actors; // 启动时 for (auto* a : g_Actors) a-Run(); // 停机时 for (auto* a : g_Actors) a-Stop();注册表让你能一键广播不用在每个地方手动调一遍。3. 状态用枚举 switch不要用一堆 boolenum class Step { 取料, 搬运, 放料, 完成 }; Step step Step::取料;而不是bool picking, bool moving, bool placing。枚举状态机天然互斥bool 组合会产生非法状态。4. 资源互斥用占用协议不要用裸 bool共享工位加owner字段SetUsedBy/ SetNotUsedBy配对使用。5. 异常停机走全局入口任意 Actor 能触发整机停不要让每个模块自己实现停机逻辑。6. 启停标志和线程存在标志分开bRunFlag业务运行中可暂停恢复bThreadExist线程是否存活退出用两个标志分开才能做到「暂停不停线程停机才退线程」。八、实际工程中的取舍与边界Actor 模型不是银弹它也有边界。适合 Actor 的工位级模块上料站、搬运手、测试台、下料站。长周期状态机一个料从进到出要几十秒以上。需要并行多个同类模块左右两条料道、多只手臂。不适合 Actor 的短任务并发图像处理、数据库写入、网络通信。这些用线程池或异步更合适。高频实时控制伺服环、运动学插补。这些要在实时线程或控制卡里做不在 Actor 层。UI 刷新用 UI 线程事件驱动不要塞进 Actor。一个成熟设备软件通常是混合模型Actor 管「业务流程和工位协作」线程池/异步管「短任务和 IO」实时任务管「伺服和插补」。不要强求一种模型通吃。九、总结从零设计一台运动控制设备软件你会自然走过「单线程 → 多线程乱 → 抽象执行单元」这三步。Actor 模型就是第三步的产物它的核心是一个独立执行单元 一个长期线程 一个状态机 一组协作接口。它解决了设备软件最头疼的三个问题状态归属状态在 Actor 内不散落全局。模块协作通过占用、握手、广播三种协议边界清晰。统一管控注册表 统一接口一键启停、一键回零、一键结批。设备行业用 Actor 不是因为保守而是因为设备软件的「多工位并行 状态机 硬件时序 安全停机」天然适合这种模型。
返回列表