从状态机视角理解程序:形式化方法如何保证复杂系统正确性

news2026/5/22 11:06:05
1. 从数学视角审视程序为什么我们需要形式化思维在软件开发的日常里我们常常埋头于代码的细节这个循环边界对不对那个指针会不会为空这个API调用返回错误该怎么处理我们依赖编译器、静态分析工具、单元测试和集成测试来捕捉这些“简单错误”。这些工具构成了我们质量保障的第一道防线也确实非常有效。然而当我们构建的系统越来越复杂尤其是涉及到并发、分布式、实时性等场景时我们会发现即使通过了所有测试系统依然可能在某个意想不到的时刻崩溃或者产生难以复现的诡异结果。这类问题往往不是语法错误或简单的逻辑漏洞而是深层次的逻辑错误比如死锁、竞态条件、活锁、数据一致性被破坏等。为什么这些错误如此棘手因为传统的测试方法本质上是“抽样检查”。你设计了一百个测试用例覆盖了你能想到的各种场景但这对于系统可能存在的天文数字般的状态组合来说只是沧海一粟。一个并发程序两个线程交错执行的顺序可能就有成千上万种一个分布式系统网络延迟、消息丢失、节点宕机等因素组合起来其状态空间几乎是无限的。依赖“经验”和“测试”来保证这类系统的正确性就像试图用渔网捞起大海里所有特定种类的鱼——你可能会捞到很多但你永远无法确信海里已经没有漏网之鱼了。这就引出了一个根本性的问题我们能否像数学家证明一个定理那样去证明我们的程序是正确的而不仅仅是“测试它看起来是对的”。答案是肯定的这条路径就是形式化方法。它不依赖于运行程序而是将程序及其期望的行为用精确的数学语言形式规约描述出来然后通过数学推理或自动化工具来验证程序的设计模型是否满足其规约。这听起来很学术但它的核心思想非常直观用数学的严谨性来弥补人类经验和测试的不足。本文的目的就是带你跳出代码实现的窠臼尝试用这种数学化的、状态机的视角来重新理解“程序”到底是什么以及如何从设计源头构筑正确性。我们将以TLA语言为例但重点不在其语法而在于理解其背后的思维方式。2. 程序本质的再思考从过程到状态我们通常把程序看作是一系列指令的序列它从第一行开始按顺序或根据条件跳转执行最终结束。这是“过程式”的视角。然而对于验证特别是并发系统的验证一个更强大的视角是“状态式”的。2.1 什么是程序的状态想象一下给程序拍快照。在执行的任何一个瞬间将所有“会变化的东西”定格下来这张照片就是程序的一个状态。哪些是“会变化的东西”主要是所有的变量包括全局变量、局部变量、堆内存对象和程序计数器PC即下一条要执行的指令位置。一个复杂的系统状态可能还包括消息队列的内容、网络连接状态、文件句柄等。例如一个简单的计数器程序int i 0; void increment() { i i 1; }它的状态可以简单地用变量i的值来表示。初始状态是{i: 0}。调用一次increment()后状态变为{i: 1}。这里我们把函数调用看作是一个引发状态变化的“动作”。2.2 状态机程序的核心模型一旦接受了“状态”的概念整个程序就可以被看作一个状态机或自动机。它包括一组可能的状态集合State Set所有可能的快照的集合。一个初始状态Initial State程序开始时的那个快照。状态转移关系Transition Relation定义了在当前状态下执行某个动作后下一个状态可能是什么。这通常是不确定的因为可能有不同的输入或内部选择。还是看计数器它的状态机非常简单状态集合{i: n}其中n是所有整数理论上无限但受限于数据类型。初始状态{i: 0}。状态转移在任何状态{i: n}下执行increment()动作会转移到状态{i: n1}。对于并发程序状态机模型的力量就显现出来了。考虑两个线程同时调用increment()int i 0; // 线程A和线程B都执行 i i 1;如果i不是原子操作通常不是它涉及读取、计算、写入三步那么交错执行就会产生问题。我们可以用状态机来枚举所有可能的交错状态需要包含i的值以及每个线程的“程序计数器”位置比如是刚要读i还是刚写完i。动作线程A执行一步或线程B执行一步。初始状态i0,A_pcstart,B_pcstart。通过枚举所有可能的动作序列即状态转移路径我们会发现存在这样一条路径两个线程都先读取i此时值为0然后分别计算得到1再先后写入最终i的结果是1而不是2。这就是一个经典的竞态条件。状态机模型清晰地揭示了所有可能的执行轨迹包括那些导致错误的轨迹。注意这种“交错式”建模是分析共享内存并发系统的经典方法。它把并发性转化为所有可能的线性交错序列让我们可以系统地分析。2.3 区分“简单错误”与“逻辑错误”基于状态机的视角我们可以更清晰地定义开篇提到的两类错误简单错误通常违反的是“局部约束”。例如数组越界访问的状态——内存地址——不在有效范围内、空指针解引用对象引用变量这个状态分量是null、除零错误计算时某个状态分量为0。这些错误往往在单个状态或单次状态转移中就能被检测出来因此静态分析、编译器检查、运行时沙箱如GC管理内存生命周期效果很好。逻辑错误违反的是“全局属性”或“时序属性”。它涉及多个状态以及状态转移的序列。安全性属性坏事永远不会发生。例如“数据一致性”要求某些状态分量之间必须始终保持某种关系如账户总额等于各分账户之和。“无死锁”要求状态机不会进入一个所有可能的后续动作都无法执行的状态。活性属性好事最终会发生。例如“最终响应”要求对于某个请求系统状态最终会转移到包含响应的状态。“无饥饿”要求某个进程最终能获得资源。逻辑错误之所以难是因为你需要审视所有可能的状态序列而不仅仅是单个状态或少数几条执行路径。人类的思维很难穷尽这些可能性尤其是在并发环境下。3. 形式化方法入门从自然语言描述到数学规约形式化方法的核心就是用数学语言来精确描述我们刚才讨论的状态机模型和想要满足的属性。自然语言如设计文档充满歧义。“系统应该高可用”——多高99.9%还是99.999%“在异常情况下保持数据一致”——哪些是异常情况怎样算一致数学语言尤其是逻辑和集合论没有歧义。形式化方法一般分为两个主要步骤3.1 第一步形式化规约用数学语言定义系统“应该做什么”和“不应该做什么”。这包括系统模型用数学定义状态变量、初始状态、以及允许的状态转移动作。这就是在写状态机。系统属性用逻辑公式通常是时序逻辑如LTL、CTL描述安全性和活性属性。例如对于一个简单的锁我们可以这样规约状态变量locked(布尔值表示锁是否被持有)owner(线程ID表示持有者可为空)。初始状态locked false,owner null。动作acquire(tid): 前提是locked false效果是locked true且owner tid。表示下一个状态的值release(tid): 前提是locked true且owner tid效果是locked false且owner null。安全性属性互斥□(locked ⇒ owner ≠ null)总是如果锁被持有则持有者非空。更关键的是□¬(locked ∧ (owner tid1) ∧ (owner tid2))总是不可能锁被持有且持有者同时是两个不同的线程。这实际上隐含在owner是单个值的定义里但更复杂的属性需要显式写出。活性属性无饥饿∀tid: ◇(acquire(tid) is enabled ⇒ ◇(acquire(tid) occurs))对于任意线程如果它无限次尝试获取锁那么最终它一定能成功。这个属性比互斥更难验证。3.2 第二步形式化验证有了数学模型和属性规约接下来就是验证模型是否满足属性。主要有两种技术模型检测适用于有限状态系统。工具如TLC for TLA Spin for Promela会自动遍历状态机所有可能到达的状态状态空间检查每一条路径是否都满足属性。如果发现违反属性的路径它会给出一个反例——导致错误的具体状态序列。这就像对状态空间进行一次“穷举测试”。优点是全自动且能提供反例缺点是受“状态空间爆炸”问题限制。定理证明适用于无限状态或极其复杂的状态系统。使用交互式定理证明器如Coq, Isabelle, ACL2工程师像数学家一样一步步地构建证明证明系统模型满足属性。这需要更多的专业知识和人力但能处理模型检测无法应对的复杂系统。4. TLA实战剖析一个分布式共识算法TLA是Leslie Lamport创建的一种形式化规约语言它基于简单的数学集合论、逻辑、函数。它不用于编写可执行代码而是用于编写系统的“蓝图”或“设计文档”——而且是机器可检查的设计文档。下面我们通过一个高度简化的“两阶段提交”协议核心模型来感受TLA的思维。4.1 两阶段提交协议简述在分布式事务中多个参与者如数据库分片需要就“提交”或“中止”一个事务达成一致。2PC是一个经典的协调协议阶段一准备阶段协调者向所有参与者发送“准备”请求。参与者执行事务操作写入日志但不提交然后回复“是”我已准备就绪或“否”。阶段二提交阶段如果协调者收到所有参与者的“是”则决定提交向所有参与者发送“提交”命令。如果协调者收到任何一个参与者的“否”或超时则决定中止向所有参与者发送“中止”命令。参与者收到“提交”则提交本地事务收到“中止”则回滚。4.2 用TLA建模2PC我们关注最核心的状态和动作忽略网络重传、日志持久化等工程细节。---- MODULE Simple2PC ---- EXTENDS Naturals, FiniteSets \* 引入自然数和有限集合模块 CONSTANT Participants \* 参与者集合例如 {DB1, DB2, DB3} VARIABLES coordinatorState, \* 协调者状态: init, waiting, committed, aborted participantState, \* 参与者状态函数: 每个参与者 - working, prepared, committed, aborted messages \* 网络中的消息集合 (***************************************************************************) (* 初始状态定义 *) (***************************************************************************) Init /\ coordinatorState init /\ participantState [p ∈ Participants |- working] \* 所有参与者初始为working /\ messages {} \* 消息队列为空 (***************************************************************************) (* 消息类型定义 *) (***************************************************************************) PrepareMsg(p) [type |- Prepare, participant |- p] VoteYesMsg(p) [type |- VoteYes, participant |- p] VoteNoMsg(p) [type |- VoteNo, participant |- p] CommitMsg [type |- Commit] AbortMsg [type |- Abort] (***************************************************************************) (* 系统可能发生的原子动作状态转移 *) (***************************************************************************) \* 1. 协调者发起准备阶段 CoordinatorSendPrepare /\ coordinatorState init /\ coordinatorState waiting /\ messages messages ∪ {PrepareMsg(p) : p ∈ Participants} \* 向所有人发送Prepare /\ UNCHANGED participantState \* 2. 参与者投票“是” ParticipantVoteYes(p) /\ participantState[p] working /\ PrepareMsg(p) ∈ messages \* 收到了Prepare消息 /\ participantState [participantState EXCEPT ![p] prepared] \* 状态变为prepared /\ messages (messages \ {PrepareMsg(p)}) ∪ {VoteYesMsg(p)} \* 消耗Prepare发出Yes /\ UNCHANGED coordinatorState \* 3. 参与者投票“否” ParticipantVoteNo(p) /\ participantState[p] working /\ PrepareMsg(p) ∈ messages /\ participantState [participantState EXCEPT ![p] aborted] \* 直接abort /\ messages (messages \ {PrepareMsg(p)}) ∪ {VoteNoMsg(p)} /\ UNCHANGED coordinatorState \* 4. 协调者收集所有Yes后决定提交 CoordinatorDecideCommit /\ coordinatorState waiting /\ ∀p ∈ Participants: VoteYesMsg(p) ∈ messages \* 收到了所有人的Yes /\ coordinatorState committed /\ messages (messages \ {VoteYesMsg(p) : p ∈ Participants}) ∪ {CommitMsg} /\ UNCHANGED participantState \* 5. 协调者收到任意No后决定中止或超时本例简化 CoordinatorDecideAbort /\ coordinatorState waiting /\ ∃p ∈ Participants: VoteNoMsg(p) ∈ messages \* 收到了至少一个No /\ coordinatorState aborted /\ messages (messages \ {VoteYesMsg(p) : p ∈ Participants}) ∪ {AbortMsg} \* 注意这里简化处理实际应移除所有相关消息 /\ UNCHANGED participantState \* 6. 参与者执行提交 ParticipantCommit(p) /\ participantState[p] prepared /\ CommitMsg ∈ messages /\ participantState [participantState EXCEPT ![p] committed] /\ messages messages \ {CommitMsg} \* 假设点对点确认简化处理 /\ UNCHANGED coordinatorState \* 7. 参与者执行中止 ParticipantAbort(p) /\ participantState[p] ∈ {working, prepared} \* working(收到abort)或prepared(未收到commit先收到abort)都可能abort /\ AbortMsg ∈ messages /\ participantState [participantState EXCEPT ![p] aborted] /\ messages messages \ {AbortMsg} /\ UNCHANGED coordinatorState (***************************************************************************) (* 定义系统的下一步状态转移关系是所有可能动作的“或” *) (***************************************************************************) Next \/ CoordinatorSendPrepare \/ ∃p ∈ Participants: ParticipantVoteYes(p) \/ ∃p ∈ Participants: ParticipantVoteNo(p) \/ CoordinatorDecideCommit \/ CoordinatorDecideAbort \/ ∃p ∈ Participants: ParticipantCommit(p) \/ ∃p ∈ Participants: ParticipantAbort(p) (***************************************************************************) (* 定义系统必须始终保持的不变量安全性属性 *) (***************************************************************************) TypeInvariant /\ coordinatorState ∈ {init, waiting, committed, aborted} /\ participantState ∈ [Participants - {working, prepared, committed, aborted}] \* 消息类型检查等... \* 最关键的一致性属性不能有些参与者提交了有些参与者却中止了。 Consistency ¬ ( (∃ p1 ∈ Participants: participantState[p1] committed) ∧ (∃ p2 ∈ Participants: participantState[p2] aborted) ) (***************************************************************************) (* 将不变量作为要验证的属性 *) (***************************************************************************) Invariants TypeInvariant ∧ Consistency 4.3 模型解析与经验心得抽象是关键TLA模型是对现实系统的抽象。我们忽略了超时重发、协调者故障、日志存储等。我们只关心核心协议逻辑在异步消息传递下的安全性。这种抽象能力是使用形式化方法最重要的技能——抓住本质忽略暂时无关的细节。状态是全局的coordinatorState,participantState,messages共同构成了系统的全局状态。TLA让我们显式地定义这些状态变量。动作是原子的每一个ParticipantVoteYes(p)这样的动作都被视为一个不可分割的原子操作。在现实中这可能需要多个步骤但我们在建模时将其压缩为一个原子步骤这简化了分析。对于并发问题我们需要仔细考虑哪些操作必须原子化才能准确建模。非确定性Next是多个可能动作的“或”。模型检查器会探索所有可能的动作序列。这完美地模拟了分布式系统的异步性消息延迟任意长、动作发生顺序不确定。属性即公式Consistency属性是一个逻辑公式。模型检查器TLC会检查从Init开始经过所有Next转移能够到达的所有状态中这个公式是否永远为真。如果为假TLC会生成一条导致违反该属性的具体执行路径反例这比在真实系统中调试一个偶发Bug要清晰无数倍。实操心得第一次用TLA建模时最常见的错误是“过度规约”或“规约不足”。过度规约会把实现细节如特定的数据结构、算法写进去导致状态空间爆炸或验证困难。规约不足则会漏掉关键的动作或前提条件使模型偏离实际系统。一个好的起点是先用自然语言和流程图把系统的“理想化”核心流程写清楚然后再逐句翻译成TLA的动作和状态。Lamport的建议是“先写注释再写代码规约”。5. 将形式化思维融入日常开发你可能会想TLA和模型检测听起来很重只有像分布式数据库、航天软件才会用吧其实形式化思维的益处远不止于使用特定工具。即使不写一行TLA代码你也可以从中汲取养分提升日常设计能力。5.1 设计阶段的状态推演在画架构图、写设计文档时有意识地思考关键组件的“状态”和“状态转移”。例如设计一个任务调度器状态任务有哪些状态Pending, Queued, Running, Succeeded, Failed, Cancelled事件/动作什么事件触发状态转移用户提交、资源就绪、执行完成、执行出错、用户取消约束不变量哪些状态组合是非法的一个任务不能同时是Running和Succeeded状态转移有哪些前提只有Pending的任务才能被取消把这些用清晰的状态图画出来并和团队成员评审往往能提前发现设计歧义和漏洞。这本质上就是在进行轻量级的、手工的形式化思考。5.2 并发代码的“状态空间”意识编写并发代码时在脑子里或纸上枚举关键共享变量的所有可能状态组合以及线程交错如何影响它们。问自己“这个锁保护了哪些状态变量”“如果在这个检查之后、操作之前状态被其他线程修改了会发生什么”这就是TOCTOU问题“是否存在一条执行路径导致所有线程都在等待对方从而死锁”这种思考方式比盲目地加锁或使用并发容器更能写出健壮的代码。5.3 定义清晰的接口契约函数的API文档可以看作是一种轻量级的形式化规约。使用前置条件requires、后置条件ensures、副作用、不变量来描述函数行为。虽然很多语言不支持像Eiffel或Spec#那样的正式契约编程但我们在注释和文档中坚持这种习惯能极大提高代码的可理解性和可维护性。例如/** * 从连接池获取一个连接。 * param timeoutMs 超时时间毫秒 * return 一个可用的数据库连接 * throws TimeoutException 如果在超时时间内无法获取连接 * throws PoolClosedException 如果连接池已关闭 * requires pool.isOpen() // 前置条件连接池必须已打开 * ensures \result.isValid() !\result.isInUse() // 后置条件返回的连接有效且未被占用 * modifies this.availableConnections // 副作用修改了可用连接集合 */ Connection getConnection(long timeoutMs) throws ...;5.4 测试用例的生成思路形式化模型可以指导测试。如果你定义了系统状态和动作理论上可以生成覆盖所有动作序列或所有重要状态组合的测试用例。虽然完全穷举不现实但可以将其作为边界你的集成测试或系统测试是否覆盖了所有重要的状态转移是否测试了所有可能导致不一致的并发交错场景模型检测工具找出的“最短反例路径”本身就是极佳的压力测试或混沌测试场景。6. 常见疑问与思维误区在实际推广和应用形式化思维时经常会遇到一些疑问和阻力这里集中探讨一下。Q1: 形式化方法太理论、太复杂学习成本高我们项目紧用不上。A: 这可能是最大的误区。你不一定要成为TLA或Coq专家才能受益。核心是状态机思维和精确规约意识。可以从一个小模块开始比如一个缓存的状态机、一个订单的状态流转。用图表和清晰的文字定义状态和转移条件。这个过程本身就能暴露很多模糊点。学习成本是分层的了解基本思想本文目标几乎无成本用到日常设计是中等收益在关键模块使用轻量级工具如TLA for 协议设计则是高收益。对于金融、基础设施等关键系统形式化验证的投入是值得的它能避免后期灾难性的修复成本。Q2: 模型是对现实的简化验证了模型正确能保证实际代码正确吗A: 不能100%但能极大提高信心。这就像建筑图纸正确不能保证施工队不犯错但没有正确的图纸房子肯定盖不好。形式化模型验证的是设计的正确性。它消除了设计层面的逻辑错误。接下来需要保证实现符合设计这可以通过代码验证、更细致的模型或高覆盖率的测试来逼近。这是一种“防御纵深”策略。先保证设计没错再对付实现错误比同时对付两者要容易得多。Q3: 状态空间爆炸怎么办模型检测还能用吗A: 状态空间爆炸是模型检测的主要挑战。但工程师有很多武器抽象合并等价状态忽略无关变量。例如如果你只关心互斥那么计数器具体值是多少可能不重要你可以抽象为“偶数”和“奇数”两个状态。对称性规约如果系统中有多个行为完全相同的进程模型检测器可以只探索等价类大幅减少状态。界限模型检测不验证无限状态只验证在某个界限内如队列长度不超过5进程数不超过3是正确的。这对于发现Bug通常足够了因为很多Bug在规模较小时就会出现。使用更强大的验证技术对于参数化系统任意多个进程可能需要定理证明或更高级的模型检测算法。Q4: 我们团队没有数学背景怎么开始A: 从“写清楚”开始。鼓励大家在设计文档中用列表和表格明确写出系统状态用键值对列出所有关键状态变量及其可能值。事件列表所有能改变系统状态的外部或内部事件。状态转移表对于每个状态事件对明确写出下一个状态是什么以及转移的前提条件。 这本身就是一种低门槛的形式化。在此基础上如果对某个复杂协议有疑问可以尝试用TLA或类似工具如Alloy建个简单模型网上有很多教程和社区支持。关键是要有追求“精确”和“无歧义”的文化。Q5: 形式化方法只适用于底层算法和协议吗A: 绝对不是。任何有明确状态和规则的系统都可以。它被成功应用于硬件电路验证、通信协议、安全协议、编程语言语义、编译器优化、甚至业务流程和工作流引擎的设计。只要你能把系统抽象成状态和动作就可以尝试用这种思维去分析和验证。回顾整篇文章我们从区分简单错误与逻辑错误开始认识到测试和经验在应对后者时的局限性。然后我们引入了状态机的视角将程序看作一系列状态的转移这为我们提供了分析复杂性的框架。接着我们介绍了形式化方法的核心——用数学语言进行规约和验证并以TLA和两阶段提交协议为例展示了如何将这一理论付诸实践。最后我们探讨了如何将形式化思维的精髓状态思考、精确规约融入日常开发而不必强求使用复杂的工具。最终的目标不是让每个人都成为形式化验证专家而是希望作为一种强大的思维工具它能帮助我们在构建复杂系统时多一份严谨少一份侥幸。在软件吞噬世界的今天这种建立在数学基础上的严谨性或许是我们对抗系统复杂性、构建可靠数字基石的唯一可靠路径。当你下次设计一个模块或审查一段并发代码时不妨先停下来问自己“它的状态机是什么样的” 这个简单的问题可能就是通往更健壮软件的第一扇门。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2634544.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…