news 2026/10/30 6:25:29

针对 `EQPBizHelper` 类的并发分析及优化意见

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
针对 `EQPBizHelper` 类的并发分析及优化意见

针对EQPBizHelper类的并发分析及优化意见

用户提供了EQPBizHelper类的完整实现,该类代表一个测试工站,与 PLC 控制器、上位机 UI、MES/EAP 等模块交互。在多工站、多硬件环境下,该类的多个方法会被不同线程调用(如硬件消息接收线程、定时模拟线程、外部业务驱动线程)。不加锁是绝对不可行的,当前代码中存在多处严重的数据竞争隐患,可能导致程序崩溃、数据错乱或死锁。

一、共享资源与并发风险点

1.SN2Duts(Dictionary<string, DUT>)
privateDictionary<string,DUT>SN2Duts
  • 写入:SOT方法中SN2Duts[dutsn] = dut;
  • 读取/删除:EOT方法中SN2Duts.ContainsKey(dutSn)、SN2Duts.Remove(dutSn)
  • 并发场景:多个托盘同时在不同工站完成测试,可能同时调用EOT;或者一个工件正在SOT添加时,另一个工件EOT尝试删除同一个键(虽然不同 SN 一般不会冲突,但 Dictionary 内部结构在多线程同时 Add/Remove 时会被破坏,引发ArgumentException或IndexOutOfRangeException)。
  • 风险:高并发下程序崩溃。
2.WaitProducingDUTs(Queue) 和ProducedDUTs(Queue)
  • 写入:SOT中不直接操作,但模拟线程SimlateProductionLoop中Dequeue;EOT中ProducedDUTs.Enqueue(dut)
  • 并发:SimlateProductionLoop线程与EOT同时操作ProducedDUTs;WaitProducingDUTs在模拟线程中Dequeue,但外部没有Enqueue(可能在其他地方),仍存在多线程读写。
  • 风险:Queue非线程安全,并发Enqueue/Dequeue导致数据损坏或异常。
3.EOTmapQueue(Queue<Dictionary<string,object>>)
privateQueue<Dictionary<string,object>>EOTmapQueue=newQueue<Dictionary<string,object>>();
  • 写入:MessageReceived中EOTmapQueue.Enqueue(map)
  • 读取:MessageReceived中EQPEOUSend分支EOTmapQueue.Dequeue()
  • 并发:两个事件都发生在硬件消息接收线程中,但可能先后触发,由于Queue非线程安全,在高频消息下可能导致枚举失败或计数错误。更危险的是,EOT方法也可能通过其他路径调用(如直接调用),但当前代码中EOT并不直接操作该队列。
4.CurrentUnloadSNMap(Dictionary<string, bool>)
  • 写入:EOT中CurrentUnloadSNMap[dutSn] = false
  • 读取:UnloadSNCheck中CurrentUnloadSNMap.ContainsKey(sn)
  • 并发:EOT可能在多线程中同时执行,多个工件完成测试时同时向该字典添加条目,造成内部状态损坏。
5.NowDoingCnt(int)
  • 读写:SOT中NowDoingCnt++;EOT中NowDoingCnt--;BeforeSOT中读取NowDoingCnt判断
  • 并发:无锁、无volatile,导致自增/自减操作不是原子的,可见性无法保证。可能产生计数错误(如同时++两次只增加一次)。
6.IsProcessing/HasCacheSpace(bool)
  • 写入:SOT和EOT中根据NowDoingCnt设置这两个标志
  • 读取:AutoBizDriver中的IsAllowProduceByCurrentDevice会读取这些标志
  • 并发:与NowDoingCnt类似,缺少同步,可能读到过期值,导致上料决策错误。
7.m_BeforeSotDoing(bool)
  • 写入:BeforeSOT中先置true,最后置false
  • 读取:BeforeSOT开头检查if (m_BeforeSotDoing) return -1;
  • 并发:虽意在防止重入,但无内存屏障,两个线程可能同时看到false并同时进入,导致重复执行。
8.SN2Site(ConcurrentDictionary)
  • 已使用ConcurrentDictionary,线程安全,但注意Clear()在EOT末尾调用,可能与其他线程的TryAdd同时进行(风险较小,但仍需注意逻辑一致性)。

二、其他隐式并发问题

1.模拟线程SimlateProductionLoop与真实硬件线程的混用
  • 当SimulateMode = true时,该线程会访问WaitProducingDUTs、ProducingDUTs、SN2Site,同时SOT、EOT也会被调用(模拟模式下也会走SOT、EOT逻辑)。两者之间没有同步,极易冲突。
2.事件回调中的跨线程 UI 操作
  • OnLog、OnException等事件可能在任意线程触发,而订阅者可能是 UI 线程。当前代码未使用Control.Invoke/BeginInvoke,可能导致 UI 异常。
3.DataWrite中操作DataStructTemplateTable
  • DataStructTemplateTable是一个DataTable,在SOT和EOT中可能被读写(SOT中创建,EOT中通过DataWrite添加行)。DataTable非线程安全,多工件并发测试会导致内部状态损坏。

三、优化方案

1.全面使用线程安全集合
现有类型推荐类型说明
Dictionary<string, DUT>ConcurrentDictionary<string, DUT>使用TryAdd、TryRemove、ContainsKey
Queue<DUT>ConcurrentQueue<DUT>使用Enqueue、TryDequeue
Queue<object>ConcurrentQueue<object>同上
Queue<Dictionary<string,object>>ConcurrentQueue<Dictionary<string,object>>同上
Dictionary<string, bool>ConcurrentDictionary<string, bool>使用TryAdd、ContainsKey
2.原子操作与内存屏障
  • 对于简单计数器NowDoingCnt,使用Interlocked.Increment/Interlocked.Decrement。
  • 对于布尔标志(如m_BeforeSotDoing),使用Interlocked.Exchange或声明为volatile并结合lock语句确保原子性。建议使用lock保护整个BeforeSOT入口。
privatereadonlyobject_beforeSotLock=newobject();publicintBeforeSOT(objectobj){lock(_beforeSotLock){if(m_BeforeSotDoing)return-1;m_BeforeSotDoing=true;// ...m_BeforeSotDoing=false;return0;}}
3.保护DataStructTemplateTable和DataWrite操作
  • 对DataTable的访问(添加列、添加行)加lock。
privatereadonlyobject_dataTableLock=newobject();lock(_dataTableLock){if(!DataStructTemplateTable.Columns.Contains(paraName))DataStructTemplateTable.Columns.Add(paraName);// 添加行等操作}
4.消除模拟线程与真实线程的共享冲突
  • 若SimulateMode为 true,应完全隔离真实硬件调用。推荐:在SimulateMode下,禁止任何外部调用SOT/EOT(通过标志控制),所有模拟逻辑仅由SimlateProductionLoop内部完成。
5.事件回调中确保 UI 安全
  • 在所有OnLog、OnException调用处,如果需要在 UI 上显示,订阅方应自行处理Invoke。但更好的做法是:在EQPBizHelper内部不假设 UI 存在,仅通过事件抛出,由上层负责线程封送。
6.使用CancellationTokenSource替代while(SimulateMode)中的无限循环
  • 便于优雅退出。
7.减少共享状态,增加不可变设计
  • 例如EOTmapQueue中存储的Dictionary对象在入队后不应再被修改。当前代码中入队后立即返回,没有修改风险,但Dequeue后直接传给EOT方法,EOT内部会读取字典内容,这是安全的。
8.对于IsProcessing和HasCacheSpace,由于它们依赖于NowDoingCnt,可以通过计算属性实时获取,而非存储状态
publicboolIsProcessing=>NowDoingCnt>0;publicboolHasCacheSpace=>NowDoingCnt<2;// 根据实际逻辑

这样避免了状态不一致。

四、典型竞态示例及修正代码

原代码(有竞态):
NowDoingCnt++;if(NowDoingCnt>0)IsProcessing=true;if(NowDoingCnt<2)HasCacheSpace=true;
修正后(原子 + 无状态标志):
intnewCount=Interlocked.Increment(refNowDoingCnt);// 不再单独维护 IsProcessing/HasCacheSpace,改用属性实时计算
原代码(Dictionary 并发):
SN2Duts[dutsn]=dut;// 可能与其他线程的 Remove 冲突
修正后:
if(!_sn2Duts.TryAdd(dutsn,dut)){Log.Warn($"Duplicate SN{dutsn}added to SN2Duts");}

五、总结

不加锁在高并发、多工站环境下必然导致数据损坏和逻辑错误。建议立即实施以下最低限度修改:

  1. 将所有普通Dictionary/Queue替换为ConcurrentDictionary/ConcurrentQueue。
  2. 对NowDoingCnt使用Interlocked操作。
  3. 对BeforeSOT的入口加lock防止重入。
  4. 对DataStructTemplateTable的所有访问加lock。
  5. 移除IsProcessing/HasCacheSpace字段,改用基于NowDoingCnt的计算属性。
  6. 在模拟模式下,禁止外部调用SOT/EOT,或者将模拟线程与真实逻辑完全分离。

经过上述优化后,系统可在多工站并行加工时保持稳定,不会因并发导致崩溃。若需要更高性能,可进一步设计无锁数据结构,但当前场景下使用Concurrent集合已足够。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 6:18:20

Pion WebRTC架构深度解析:Go语言WebRTC实现原理完全指南

Pion WebRTC架构深度解析&#xff1a;Go语言WebRTC实现原理完全指南 Pion WebRTC是一个纯Go语言实现的WebRTC API库&#xff0c;为开发者提供了构建实时音视频通信应用的完整解决方案。作为WebRTC技术的Go语言实现&#xff0c;Pion WebRTC让开发者能够在任何支持Go的环境中轻松…

作者头像 李华
网站建设 2026/10/30 6:18:19

Pion WebRTC跨平台开发终极指南:Windows、Linux、macOS全支持

Pion WebRTC跨平台开发终极指南&#xff1a;Windows、Linux、macOS全支持 想要构建跨平台的实时音视频应用吗&#xff1f;Pion WebRTC是一个纯Go语言实现的WebRTC API库&#xff0c;为开发者提供了强大的跨平台支持。无论你是Windows用户、Linux开发者还是macOS爱好者&#xf…

作者头像 李华
网站建设 2026/10/30 6:12:25

在M1 MacOS上构建ztm项目时解决arm64架构的未定义符号问题

在M1 MacOS上构建ztm项目时解决arm64架构的未定义符号问题 【免费下载链接】ztm ZTM&#xff08;零信任网格&#xff09;是一个以隐私为先的开源去中心化端到端加密软件定义网络&#xff0c;基于HTTP/2隧道。体验无限连接&#xff0c;将全球网格化&#xff01; 项目地址: htt…

作者头像 李华
网站建设 2026/10/30 6:09:20

Linux内核优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/30 6:07:55

in + Vue3 + ElementPlus 实践:多租户下的数据越权防护

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/30 6:07:29

Gin + Vue3 + ElementPlus 接口分页与筛选参数规范

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华