贴旧作:把状态机呈现给用户是拙劣的设计--从历史角度再论“状态机”

本文探讨了IVR设计中状态机的复杂性及其对开发者带来的挑战。通过对比事件驱动及轮询模式下的API开发,分析了状态机编程模式的难点,并介绍了多线程编程模式如何改善这些问题。
    在IVR设计中,在早期采用状态机是无奈的选择,对应用程序的开发者而言(以下也称“用户”),状态机实际上是很难理解的概念,也是造成IVR设计复杂性的根源。不过这也给Intel CT ADE、蓝星际Koodoo语言一类的IVR开发工具带来了巨大的市场空间。
    提供给用户类似高级语言而非状态机的用户界面,给用户带来了很大的便利,但要实现这么一个带有强大编译功能的语音平台并不容易,其难度远远超过状态机实现,需要较强的开发实力,对平台厂商是个很大的挑战。


    为什么说状态机复杂的?

    这要从IVR语音开发的原始方式说起。作为语音板卡厂商基本上提供了两种类型的API,一种是以Dialogic为代表的事件驱动模式,另外一种是以国内厂商如东进、三汇的轮询模式;当然Dialogic接口较为丰富,除了事件驱动模式外也支持轮询等模式。
    大家也许没有意识到,传统方法上采用这两种模式的API开发应用程序其实都是状态机。
    所谓状态机(或有限状态机,即FSM),是指“用一组可能的状态来描述系统行为,系统在任何时刻只能处于其中的一个状态。也可以描述由输入值决定的状态转移。最后可以描述在某个状态下或状态转移期间可能发生的操作。”

    先来看看一段典型的Dialogic例子程序:
    (有关Dialogic的代码均摘自msidemo.c,版权属于Intel公司)
TABLE table[]=
  {/*current_state event       next_stat   function */
  { ST_WTRING,     DE_RINGS,   ST_OFFHOOK, setoffhk  },
  { ST_OFFHOOK,    DX_OFFHOOK, ST_PLAY,    play      },
  { ST_OFFHOOK,    DE_LCOFF,   ST_ONHOOK,  sethook   },
  { ST_PLAY,       TM_EOD,     ST_GETDIG,  get_digits},
  { ST_PLAY,       TM_MAXDTMF, ST_GETDIG,  get_digits},
  ...
  };
    这个结构描述了一个状态机,每一个状态都有状态名字如ST_WTRING,
事件如DE_RINGS,本状态完成后即将转移的下一个状态如ST_OFFHOOK,本状态对应的动作(函数)如setoffhk等等。作为一个最简单演示基本功能的程序其状态就有55个之多。
    驱动这些事件的核心是check_event()函数, 循环调用下列代码:
    if(dxinfo[channel].state == table[i].current_state
          && event == table[i].event){
       // 找到当前状态下对应的动作
       func_ptr = table[i].funcptr;
       dxinfo[channel].state = table[i].next_state;
       (*func_ptr)(channel);  // 执行这个动作
       ...
     }
     而执行的动作之中会根据情况,改变通道的状态。
     请注意,因为是多线路并发执行,所以几乎任何语音操作都是异步的,不允许任何的堵塞。

    好,我们再看看东进公司的一段例子程序:
    (有关东进的代码均摘自Dial/D.c,版权属于东进公司)
void WINAPI yzDoWork()
{
  ...
  for(int Line=0;Line<TotalLine;Line++){
     yzDrawState(Line);  //draw
     switch(Lines[Line].State){  //state transfer
        case CH_FREE:
           break;
        case CH_DIAL:
           if(CheckSendEnd(Line) == 1){
              StartSigCheck(Line);
              Lines[Line].State=CH_CHECKSIG;
           }
           break;
        case CH_CHECKSIG:
           tt = Sig_CheckDial(Line);
           if(tt == S_BUSY)
              Lines[Line].State = CH_BUSY;
           else if(tt == S_CONNECT)
              Lines[Line].State = CH_CONNECT;
           else if(tt == S_NOSIGNAL)
              Lines[Line].State= CH_NOSIGNAL;
           break;
        case CH_BUSY:
        case CH_NOSIGNAL:
           ...
     }
  }
}

    这也是一个典型的状态机,标识了很多状态,然后在每个状态下执行响应的操作,并且改变其状态--迁移到下一个状态。

    采样这种状态机的理由是,语音系统往往通道很多,每个通道看起来是并发操作,所以最简单是实现就是每个通道保存自己当前的状态,并进行迁移。
因为只有一个控制线程(或进程),所以每个状态下的操作不允许堵塞,如果某个线程执行一个耗时半分钟的操作,其它所有的线路将会同时引起停顿。
    我们也可以把状态看成是个时间片,你必须精心地划分好时间片,让操作足够地短。这类似早期的Windows3.x操作系统,是非抢占式的,所以把状态看成是命名的消息也是可以的。这类系统总有一个事件处理函数,去处理这些系统消息或用户消息(状态)。

    开发者为什么普遍觉得这样的程序难写?
    首先,如果应用复杂,状态是非常多的,经常达到数千个,开发者要仔细地划分这些状态是很大的工作量。
    其次,这些状态混在一起,没有层次,很难管理。因为这所有的状态地位都是平等的,是线性关系。这样的代码实际上也很难维护,造成了语音开发的门槛。
    第三,因为上述第二点的原因,业务操作的代码也只好和语音操作的代码混在一起,并且要强行对业务代码进行也进行状态划分,还需要小心避免业务的长操作。
    第四,造成应用开发人员被迫进行底层思维,比如一个放音操作,要人为地分解为1、开始放音,2、判断有没有放完,3、有没有被按键打断等等。
    第五,当线路较多时,容易造成性能的急剧下降。这主要是循环处理造成的。
    第六,流程的可读性变差,因为状态可以随意跳转而由于处理是线性结构很难看出流程的实际走向。
    第七,很难单步跟踪调试。
    第八,不容易以直观的方式实现循环,而很多业务实际上是需要限制次数的,如密码不对后的几次身份验证,重复播音次数,语音功能菜单最多操作次数等。

    目前市面上以新太为代表的语音平台产品,还是以状态机为核心,上述弊端基本上都存在,给客户带来的唯一方便就是避免了对底层板卡API编程,思维方式并没有变化。
    笔者在以前撰文指出过的新太脚本形同汇编,就是其死抱状态机教条带来的恶果,再怎么图形化也没有用。

    实际上,现代操作系统的发展和语音板卡API的发展给语音开发带来了全新的编程模式,这就是基于多线程的编程模式。这种编程思想的最基本出发点就是,把单一的通道限定在单一的线程之中执行,这样完全可以不必考虑时间片、消息、状态等额外的东西,语音操作也可以使用同步堵塞操作了,既符合程序员的思维,也符合业务流程的自然流向,并且可以彻底实现底层操作和业务操作的分离。
    以Koodoo语言为例子(Intel CT ADE类似),每个语音通道相当于一个虚拟机,虚拟机执行以Koodoo语言编制的业务流程脚本。而Koodoo语言具有现代高级语言的特性。这样彻底摆脱了状态机的桎梏,实现了语音开发的根本性的变化。

企业创新活动具有投入周期长、不确定性高和收益实现滞后等特征,持续稳定的资源支持是保障企业长期创新的重要基础。耐心资本作为一种强调长期价值创造、具备较高风险容忍度并积极参与企业治理的资本形态,能够通过缓解融资约束、优化公司治理结构以及增强企业风险承担能力,为企业持续开展创新活动提供长期稳定支持 本文基于2010—2024年中国A股上市公司样本数据,借鉴《耐心资本对企业持续性创新投入的影响研究》一文中的基准回归设计思路和研究方法,围绕“耐心资本是否能够促进企业持续性创新投入”这一问题展开基准回归实证检验,基准回归结果显示,耐心资本能显著促进企业持续性创新,数据集含原始数据、处理代码、基准回归实证结果 关键指标构建: 1.耐心资本:本文从稳定型股权和关系型债权两个维度刻画企业耐心资本水平,并采用熵权法对两个指标进行加权整合,构建综合耐心资本指数。其中,稳定型股权参考温磊和李思飞(2024)的研究,以长期机构投资者持股比例作为衡量指标;关系型债权参考吴旻佳(2022)、姜中裕(2024)的研究,采用上市公司长期负债占负债总额的比例衡量 2.企业持续性创新:基于研发投入三期动态变化构建,借鉴何郁冰(2017)、杨仁发(2025)的研究思路,计算第t-1至t年研发投入之和与第t-2至t-1年研发投入之和的比值,再将该比值乘以第t-1至t年研发投入之和,以此反映企业在创新投入上的持续性特征 相关数据:上市公司耐心资本数据,上市公司耐心资本投资数据,上市公司研发投入与专利数据 一、数据介绍 数据名称:耐心资本对企业持续性创新投入的影响研究 数据范围:上市公司企业 时间范围:2010-2024年 样本数量:31725条 数据来源:上市公司年报 数据说明:含原始数据、处理过程dofile文件、基准回归结果
内容概要:本文聚焦于语音增强领域的组稀疏信号去噪技术,深入研究了结合非凸正则化与凸优化的先进去噪方法,并提供了完整的Matlab代码实现方案。研究通过构建组稀疏信号模型,设计高效的非凸正则项以增强稀疏性表达能力,进而将其融入凸优化框架中求解,从而在复杂噪声环境下有效提升语音信号的清晰度与质量。文章不仅详述了算法的数学推导与优化求解流程,还突出了该方法在保留语音关键特征的同时抑制噪声的优越性能。此外,文档还列举了多个相关科研方向,展现出信号处理与优化理论在智能优化、机器学习、电力系统等多学科交叉应用中的广阔前景。; 适合人群:具备信号处理、优化理论或机器学习基础知识,从事语音增强、通信工程、电子信息、自动化等相关领域的研究生、科研人员及工程技术人员。; 使用场景及目标:① 深入理解非凸正则化在稀疏信号恢复中的理论优势与实现机制;② 实践并复现组稀疏信号去噪算法,开展不同噪声条件下的性能对比实验;③ 利用Matlab平台完成语音增强相关的科研课题、课程设计或算法开发。; 阅读建议:建议读者结合文中的Matlab代码进行动手实践,重点关注目标函数的构造、优化算法的迭代过程及参数调优策略。初学者应先夯实稀疏表示与凸优化的基础知识,再循序渐进地掌握非凸正则化的核心思想与实现细节,以充分发挥该方法的技术潜力。
Cloudflare Computer 是一个运行在 Durable Object 内部的虚拟文件系统。Durable Object 通过 SQLite 保存权威状态,并通过 workspace.runtime 提供一个可插拔的执行接口。目前提供三种后端: 容器(Container):将 SQLite 状态通过 FUSE 挂载到沙箱容器中。沙箱侧的守护进程(computerd)将状态挂载为文件系统,并通过 capnweb RPC 通道同步变更。完整的 Linux 用户空间、真实的二进制文件、真实的网络环境。 隔离壳(Isolate shell):在动态 Worker 中运行 just-bash。它通过 Workers RPC 访问权威工作区,因此不存在第二个存储或同步往返。 隔离 JavaScript(Isolate JavaScript):在全新的动态 Worker 中运行 ECMAScript 模块,支持结构化输入/结果、持久化相对导入、配置库、工作区支持的 node:fs/promises,以及受信任的 ws:git 和 ws:artifacts 模块。 工作区可以在稳定 ID 下注册多个后端。workspace.runtime.exec(source, { backend }) 是唯一的执行入口点;所选后端决定 source 是 shell 命令还是 ECMAScript 模块。后端在首次使用时延迟连接。 工作区也可以完全不依赖后端构建,仅向调用者提供文件系统本身。 Important 仅预览版 此软件包仅作为预览版提供,用于收集反馈。API 不稳定,设计可能会发生变化。 适用于实验、探索和原型开发。目前不适合用于生产环境。 docs/ 目录下的规范具有前瞻性——请将其视为设计意图,而非当前代码的描述。 使用方法 如果您想基于 Cloudflare C
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值