深入理解嵌入式中重要的编程模型

大家好,我是写代码的篮球球痴
今天我们看一看业界一些著名的编程模型

背景

模型是对事物共性的抽象,编程模型就是对编程的共性的抽象。


什么是编程的共性呢?

最重要的共性就是:程序设计时,代码的抽象方式、组织方式或复用方式。编程模型主要是方法与思想。编程模型处于方法或思想性的层面,在很多情况下,也可称为编程方法、编程方式、编程模式或编程技术、编程范式。在这里就当做同一种说法。

当面对一个新问题时,通常的想法是通过分析,不断的转化和转换,得到本质相同的熟悉的、或抽象的、简单的一个问题,这就是化归思想。把初始的问题或对象称为原型,把化归后的相对定型的模拟化或理想化的对象称为模型

编程模型,简单地可以理解它就是模板,遇到相似问题就可以方便依模板解决,这样就简化了编程问题。不同的编程环境和不同的应用对象有不同的编程模型。


0e216837e907a0fdba3ae86461a4b8b5.png


事件驱动

174f0877dc70adfca06233d1f3a14b67.png

来源于《Software Architecture Patterns》

事件驱动架构(Event-Driven Architecture)是一种用于设计应用的软件架构和模型,程序的执行流由外部事件来决定,它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。主要包括 4 个基本组件:

  • 事件队列(event queue):接收事件的入口,存储待处理事件

  • 分发器(event mediator):将不同的事件分发到不同的业务逻辑单元

  • 事件通道(event channel):分发器与处理器之间的联系渠道

  • 事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操作

为什么采用事件驱动模型?

事件驱动模型也就是我们常说的观察者,或者发布-订阅模型;

87008d363eb76a0170409452a5d16a8e.png

理解它的几个关键点:

  • 首先是一种对象间的一对多的关系;最简单的如交通信号灯,信号灯是目标(一方),行人注视着信号灯(多方);

  • 当目标发送改变(发布),观察者(订阅者)就可以接收到改变;

  • 观察者如何处理(如行人如何走,是快走/慢走/不走,目标不会管的),目标无需干涉;所以就松散耦合了它们之间的关系。

许多现代应用设计都是由事件驱动的,事件驱动应用可以用任何一种编程语言来创建,因为事件驱动本身是一种编程方法,而不是一种编程语言。

  • 松耦合——服务不需要(也不应该)知道或依赖于其他服务。在使用事件时,服务独立运行,不了解其他服务,包括其实现细节和传输协议。事件模型下的服务可以独立地、更容易地更新、测试和部署。

  • 易扩展——通过高度独立和解耦的事件处理器自然地实现了可扩展性。每个事件处理器都可以单独扩展,从而实现细粒度的可扩展性。

  • 恢复支持——带有队列的事件驱动架构可以通过“重播”过去的事件来恢复丢失的工作。当用户需要恢复时,这对于防止数据丢失非常有用。

事件驱动架构可以最大程度减少耦合度,因此是现代化分布式应用架构的理想之选。

深入理解事件驱动

1.异步处理和主动轮训,要理解事件驱动和程序,就需要与非事件驱动的程序进行比较。实际上,现代的程序大多是事件驱动的,比如多线程的程序,肯定是事件驱动的。早期则存在许多非事件驱动的程序,这样的程序,在需要等待某个条件触发时,会不断地检查这个条件,直到条件满足,这是很浪费cpu时间的。而事件驱动的程序,则有机会释放cpu从而进入睡眠态(注意是有机会,当然程序也可自行决定不释放cpu),当事件触发时被操作系统唤醒,这样就能更加有效地使用cpu。

2.IO模型,事件驱动框架一般是采用Reactor模式或者Proactor模式的IO模型。

Reactor模式其中非常重要的一环就是调用函数来完成数据拷贝,这部分是应用程序自己完成的,内核只负责通知监控的事件到来了,所以本质上Reactor模式属于非阻塞同步IO。

5786886e9433036cda8cb87b074ffccf.png

来自:深入理解Linux高性能网络架构的那些事

Proactor模式,借助于系统本身的异步IO特性,由操作系统进行数据拷贝,在完成之后来通知应用程序来取就可以,效率更高一些,但是底层需要借助于内核的异步IO机制来实现,可能借助于DMA和Zero-Copy技术来实现,理论上性能更高。

当前Windows系统通过IOCP实现了真正的异步I/O,而在Linux 系统的异步I/O还不完善,比如Linux中的boost.asio模块就是异步IO的支持,但是目前Linux系统还是以基于Reactor模式的非阻塞同步IO为主。

3.事件队列,事件驱动的程序必定会直接或者间接拥有一个事件队列,用于存储未能及时处理的事件,这个事件队列,可以采用消息队列。

4.事件串联,事件驱动的程序的行为,完全受外部输入的事件控制,所以事件驱动框架中,存在大量处理程序逻辑,可以通过事件把各个处理流程关联起来。

5.顺序性和原子化,事件驱动的程序可以按照一定的顺序处理队列中的事件,而这个顺序则是由事件的触发顺序决定的,这一特性往往被用于保证某些过程的顺序性和原子化。

事件驱动的缺点

  • 事件驱动架构,就是通过引入中间层 来实现事件发布-订阅机制进行组件解耦,看似能带来不少诱人的优点,也必然会增加系统的复杂度,间接增加开发难度和维护难度。

  • 事件驱动架构改变了编程思维,将完整的功能过程,拆解为了不同的异步事件处理,也丧失了连贯的流程处理能力。如果事件数量众多,就容易在“事件丛林”中迷了路,比如中断风暴,惊群效应等。

常用的事件驱动框架
  • select
  • poll
  • epoll
  • libev

  • 中断系统

消息驱动

b6f87f532f0842d82c24949e3135525a.png

消息驱动事件驱动很类似,都是先有一个事件,然后产生一个相应的消息,再把消息放入消息队列,由需要的项目获取。他们只是一些细微区别,一般都采用相同框架,细微的区别:

消息驱动:生产者A发送一个消息到消息队列,消费者B收到该消息。生产者A很明确这个消息是发给消费者B的。通常是P2P模式。

事件驱动:生产者A发出一个事件,消费者B或者消费者C收到这个事件,或者没人收到这个事件,生产者A只会产生一个事件,不关心谁会处理这个事件 ,通常是发布-订阅模型。

现代软件系统是跨多个端点运行并通过大型网络连接的分布式系统。例如,考虑一位航空公司客户通过 Web 浏览器购买机票。该订单可能会通过API,然后通过一系列返回结果的过程。这些来回通信的一个术语是消息传递。在消息驱动架构中,这些 API 调用看起来非常像一个函数调用:API 知道它在调用什么,期待某个结果并等待该结果。

消息驱动的优点

  • 开发难度低:消息驱动类似经典的编程模型,调用一个函数,等待一个结果,对结果做一些事情,编程简单快速,开发难度低。

  • 方便调试维护:因为编程逻辑清晰简单,流程清晰,调试起来更加直接方便,后期维护也容易。

常用的消息驱动框架
  • API网关
  • gRPC
  • 微服务架构

事件驱动vs消息驱动

消息驱动的方法与事件驱动的方法一样有很多优点和缺点,但每种方法都有自己最适合的情况。

消息感觉很像经典的编程模型:调用一个函数,等待一个结果,对结果做一些事情。除了为大多数程序员所熟悉之外,这种结构还可以使调试更加直接。另一个优点是消息“阻塞”,这意味着呼叫和响应的各个单元坐下来等待轮到接收者进行处理。

事件驱动系统使单个事件易于隔离测试。然而,这种与整个应用系统的分离也抑制了这些单元报告错误、重试调用程序甚至只是向用户确认进程已完成的能力。换句话说:当事件驱动系统中发生错误时,很难追踪到底是哪里出了问题。可观察性工具正在应对调试复杂事件链的挑战。但是,添加到业务交易交叉点的每个工具都会为负责管理这些工作流的程序员带来另一层复杂性。

如果通信通常以一对一的方式进行,并且优先接收定期状态更新或确认,那么您将倾向于使用基于消息的方法。但是,如果系统之间的交互特别复杂,并且确认和状态更新导致的延迟使得等待它们变得不切实际,那么事件驱动的设计可能更合适。但是请记住,大多数大型组织最终会采用混合策略,一些面向客户/API 调用使用消息驱动,而企业本身使用事件驱动。因此,尽可能多地熟悉两者并没有什么坏处。

数据驱动

数据驱动核心出发点是相对于程序逻辑,人类更擅长于处理数据。数据比程序逻辑更容易驾驭,所以我们应该尽可能的将设计的复杂度从程序代码转移至数据。

例子

假设有一个程序,需要处理其他程序发送的消息,消息类型是字符串,每个消息都需要一个函数进行处理。第一印象,我们可能会这样处理:

e1e92281a1a5dbe0afd5a2b062ee641c.png

上面的消息类型取自sip协议(不完全相同,sip协议借鉴了http协议),消息类型可能还会增加。看着常常的流程可能有点累,检测一下中间某个消息有没有处理也比较费劲,而且,每增加一个消息,就要增加一个流程分支。

按照数据驱动编程的思路,可能会这样设计:

716f3ba3d593e2ce26ab6210f3d53fa9.png

下面这种思路的优势:

1、可读性更强,消息处理流程一目了然。

2、更容易修改,要增加新的消息,只要修改数据即可,不需要修改流程。

3、重用,第一种方案的很多的else if其实只是消息类型和处理函数不同,但是逻辑是一样的。下面的这种方案就是将这种相同的逻辑提取出来,而把容易发生变化的部分提到外面。

隐含在背后的思想

很多设计思路背后的原理其实都是相通的,隐含在数据驱动编程背后的实现思想包括:

1、控制复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。

2、隔离变化。像上面的例子,每个消息处理的逻辑是不变的,但是消息可能是变化的,那就把容易变化的消息和不容易变化的逻辑分离。

3、机制和策略的分离。和第二点很像,本书中很多地方提到了机制和策略。上例中,我的理解,机制就是消息的处理逻辑,策略就是不同的消息处理:

83140cb9f4a9fd78aaa2e8496b5ed8cb.png

深入理解编程艺术之策略与机制相分离

数据驱动编程可以用来做什么

  1. 表驱动法(Table-Driven)

    消除重复代码,考虑一个消息(事件)驱动的系统,系统的某一模块需要和其他的几个模块进行通信。它收到消息后,需要根据消息的发送方,消息的类型,自身的状态,进行不同的处理。比较常见的一个做法是用三个级联的switch分支实现通过硬编码来实现:

    switch(sendMode)
    {
    case:
    }
    switch(msgEvent)
    {
    case:
    }
    switch(myStatus)
    {
    case:
    }

这种方法的缺点:

  • 可读性不高:找一个消息的处理部分代码需要跳转多层代码。

  • 过多的switch分支,这其实也是一种重复代码。他们都有共同的特性,还   可以再进一步进行提炼。

  • 可扩展性差:如果为程序增加一种新的模块的状态,这可能要改变所有的  消息处理的函数,非常的不方便,而且过程容易出错。

  • 程序缺少核心主干:缺少一个能够提纲挈领的主干,程序的主干被淹没在    大量的代码逻辑之中。

用表驱动法来实现

根据定义的三个枚举:模块类型,消息类型,自身模块状态,定义一个函数跳转表:

typedef struct  __EVENT_DRIVE
{MODE_TYPE mod;//消息的发送模块EVENT_TYPE event;//消息类型STATUS_TYPE status;//自身状态EVENT_FUN eventfun;//此状态下的处理函数指针
}EVENT_DRIVE;EVENT_DRIVE eventdriver[] = //这就是一张表的定义,不一定是数据库中的表。也可以使自己定义的一个结构体数组。
{{MODE_A, EVENT_a, STATUS_1, fun1}{MODE_A, EVENT_a, STATUS_2, fun2}{MODE_A, EVENT_a, STATUS_3, fun3}{MODE_A, EVENT_b, STATUS_1, fun4}{MODE_A, EVENT_b, STATUS_2, fun5}{MODE_B, EVENT_a, STATUS_1, fun6}{MODE_B, EVENT_a, STATUS_2, fun7}{MODE_B, EVENT_a, STATUS_3, fun8}{MODE_B, EVENT_b, STATUS_1, fun9}{MODE_B, EVENT_b, STATUS_2, fun10}
};int driversize = sizeof(eventdriver) / sizeof(EVENT_DRIVE)//驱动表的大小EVENT_FUN GetFunFromDriver(MODE_TYPE mod, EVENT_TYPE event, STATUS_TYPE status)//驱动表查找函数
{
int i = 0;
for (i = 0; i < driversize; i ++){
if ((eventdriver[i].mod == mod) && (eventdriver[i].event == event) && (eventdriver[i].status == status)){
return eventdriver[i].eventfun;}}
return NULL;
}

这种方法的好处:

  • 提高了程序的可读性。一个消息如何处理,只要看一下驱动表就知道,非常明显。

  • 减少了重复代码。这种方法的代码量肯定比第一种少。为什么?因为它把一些重复的东西:switch分支处理进行了抽象,把其中公共的东西——根据三个元素查找处理方法抽象成了一个函数GetFunFromDriver外加一个驱动表。

  • 可扩展性。注意这个函数指针,他的定义其实就是一种契约,类似于java中的接口,c++中的纯虚函数,只有满足这个条件(入参,返回值),才可以作为一个事件的处理函数。这个有一点插件结构的味道,你可以对这些插件进行方便替换,新增,删除,从而改变程序的行为。而这种改变,对事件处理函数的查找又是隔离的(也可以叫做隔离了变化)。、

  • 程序有一个明显的清晰主干

  • 降低了复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。

 2. 基于数据模型编程

  • 基于Yang模型编程(DSL),YANG是一种语言,是用来建立数据模型的语言,可以通过定义业务数据模型,自动生成对应数据处理逻辑(比如参数校验,范围,存储方式,权限控制等),典型的数据驱动编程;

  • Linux内核DTS设备树模型,删除大量hardcode,精简内核驱动代码。

  • 基于xml,protobuf数据模型编程,界面显示,web配置逻辑,RPC微服务等;

数据驱动思考

  • 它不是一个全新的编程模型:它只是一种设计思路,而且历史悠久,在unix/linux社区应用很多;

  • 它不同于面向对象设计中的数据:“数据驱动编程中,数据不但表示了某个对象的状态,实际上还定义了程序的流程;OO看重的是封装,而数据驱动编程看重的是编写尽可能少的代码。”

  • 数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。——Rob Pike

  • 程序员束手无策,只有跳脱代码,直起腰,仔细思考数据才是最好的行动。表达式编程的精髓。——Fred Brooks

  • 数据比程序逻辑更易驾驭。尽可能把设计的复杂度从代码转移至数据是个好实践。——《unix编程艺术》作者。

总结

设计模式(古典)主要针对OOP领域编程设计方法的抽象。这里的编程模型,主要是针对业务编程框架的抽象。

消息驱动事件驱动,本身有很多相似地方,消息驱动主要代表是经典跨进程通信架构,让消息处理和函数调用一样,逻辑依然可以保持清晰简单。而事件驱动采取异步处理方式,最大化解耦,让程序耦合更低,框架更易扩展,两种编程模型都有各自优缺点,只有根据具体的场景找到一种合适使用方法。

数据驱动是一种新的编程思考,坚持"data as program"准则,把处理逻辑数据化,这样可以通过不同数据配置来实现不同的逻辑,让核心代码更精炼简单,框架更易扩展。

参考和扩展阅读

  • 《unix编程艺术》

  • 《Software Architecture Patterns》

  •  https://blog.csdn.net/chgaowei/article/details/6966857

  • 深入理解Linux高性能网络架构的那些事

- END -



推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

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

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

相关文章

android v4包自动导入吧,android如何导入v4包的源码

1.我们导入v4包源码却发现没有导入按钮当我们调用android-support-v4.jar里面的控件的时候(这里以android.support.v4.view.ViewPager举例说明)&#xff0c;很多时候还需要查看此控件的源码&#xff0c;我们按住Ctrl键点击如下图中的ViewPager之后会出现如下提示出现这个问题的…

【floyd】【bitset】洛谷 P1841 [JSOI2007]重要的城市 题解

bitset玄学完美优化复杂度&#xff1f; 题目描述 参加jsoi冬令营的同学最近发现&#xff0c;由于南航校内修路截断了原来通向计算中心的路&#xff0c;导致去的路程比原先增加了近一公里。而食堂门前施工虽然也截断了原来通向计算中心的路&#xff0c;却没有使路程增加&#xf…

新风口下:嵌入式AI学习中较好的练手项目(附代码资料/学习视频/学习规划)...

有粉丝问我&#xff1a;“当前乃至未来5-10年&#xff0c;嵌入式开发者还有哪些风口&#xff1f;”画外音&#xff1a;风口的本质&#xff0c;其实就是一段时间的人才供需不平衡。说白了就是由于行业突变&#xff0c;敏锐的资本快速进入&#xff0c;导致短时间内行业大量扩张&a…

Windows 任务栏缩略图自定义程序[更新 Build20100830]

很久没有写一点小玩意儿了&#xff0c;今天终于有了一次机会。这个程序能够对 Windows 7 中的任务栏实时预览缩略图进行一系列个性化的调整&#xff0c;使其使用起来更炫更方便&#xff0c;避免了不方便的注册表修改操作&#xff0c;将其转化为方便图形界面&#xff0c;只需要点…

我接的是地啊,不,你接的是土!

作者&#xff1a;晓宇&#xff0c;排版&#xff1a;晓宇微信公众号&#xff1a;芯片之家&#xff08;ID&#xff1a;chiphome-dy&#xff09;1、我接地了啊&#xff0c;电子设计中&#xff0c;接地是非常重要的&#xff0c;地可不等于土&#xff0c;哈哈&#xff0c;有效的接地…

邻接矩阵-建立图

1.介绍图的相关概念 图是由顶点的有穷非空集和一个描述顶点之间关系-边&#xff08;或者弧&#xff09;的集合组成。通常&#xff0c;图中的数据元素被称为顶点&#xff0c;顶点间的关系用边表示&#xff0c;图通常用字母G表示&#xff0c;图的顶点通常用字母V表示&#xff0c;…

Busybox 制作文件系统并用 Qemu 启动编译的内核镜像

编译内核操作&#xff1a;https://blog.csdn.net/assiduous_me/article/details/120938556安装Busybox操作&#xff1a;https://blog.csdn.net/assiduous_me/article/details/120939319syzDESKTOP-B10G93S:~$ ls -l total 20 drwxr-xr-x 44 syz syz 4096 Oct 26 22:05 busybox …

深入浅出Win32多线程程序设计之线程通信

简介  线程之间通信的两个基本问题是互斥和同步。  线程同步是指线程之间所具有的一种制约关系&#xff0c;一个线程的执行依赖另一个线程的消息&#xff0c;当它没有得到另一个线程的消息时应等待&#xff0c;直到消息到达时才被唤醒。  线程互斥是指对于共享的操作系统…

Kafka Producer源码简述

接着上文kafka的简述&#xff0c;这一章我们一探kafka生产者是如何发送消息到消息服务器的。 代码的入口还是从 kafkaTemplate.send开始 最终我们就会到 org.springframework.kafka.core.KafkaTemplate#doSend方法 这里的关键就是 org.apache.kafka.clients.producer.Producer#…

原来搞单片机也可以面向对象

摘要&#xff1a;在看别人单片机程序时&#xff0c;你也许是奔溃的&#xff0c;因为全局变量满天飞&#xff0c;不知道哪个在哪用了&#xff0c;哪个表示什么&#xff0c;而且编写极其不规范。自己写单片机程序时&#xff0c;也许你也是奔溃的。总感觉重新开启一个项目&#xf…

雅虎年底升级IPv6标准 100万用户恐受影响

雅虎年底升级IPv6标准 100万用户恐受影响 http://network.51cto.com 2011-01-20 17:34 佚名 cnBeta 我要评论(0) 据国外媒体报道&#xff0c;雅虎计划今年年底将主站点Yahoo.com升级为IPv6标准&#xff0c;此举可能会使约100万用户在初期无法访问雅虎站点。据国外媒体报道&…

Linux v4l2框架分析

背景说明&#xff1a;Kernel版本&#xff1a;4.14ARM64处理器&#xff0c;Contex-A53&#xff0c;双核使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio1. 概述V4L2(Video for Linux 2)&#xff1a;Linux内核中关于视频设备驱动的框架&#xff0c;对上向应用层提供统…

JAVA自学笔记23

JAVA自学笔记23 1、多线程 1&#xff09;引入&#xff1a; 2&#xff09;进程 是正在运行的程序。是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。 多进程&#xff1a; 单进程的计算机只能做一件事情&#xff0c;而现在的计算机都可以做…

上午写了一段代码,下午就被开除了~

俗话说得好&#xff0c;“代码写的少&#xff0c;离职少不了”。最近畅游互联网&#xff0c;发现一些离职小技巧&#xff0c;读后&#xff0c;内心被深深地打动了……但是&#xff0c;细细品过之后&#xff0c;发现对我们程序员不太适用了。例如&#xff1a;领导夹菜你转桌&…

nginx加载html目录下图片,nginx配置访问图片路径以及html静态页面的调取方法

nginx配置访问图片路径以及html静态页面的调取方法发布时间&#xff1a;2017-03-09 12:06来源&#xff1a;互联网当前栏目&#xff1a;web技术类给大家讲一个快速配置nginx访问图片地址&#xff0c;以及访问html静态页面的配置。1.实验环境首先随便某个路径下创建相应的目录。如…

微信小程序继续入坑指南

微信小程序继续入坑指南 wxml 类似于html 感觉和ejs灰常的相似 数据绑定 js Page({data: {message: "hello world"} })wxml <view>{{message}}</view> 使用的是https://mustache.github.io/模板引擎系统 对组件的属性和控制属性的更改 <view id"…

思科收购网络安全管理厂商Pari Networks

思科收购网络安全管理厂商Pari Networkshttp://netsecurity.51cto.com 2011-01-28 09:39 胡杨 译 网界网 我要评论(0)摘要&#xff1a;思科本星期宣布&#xff0c;它打算收购私营企业Pari Networks。这个企业是前思科工程师创建的&#xff0c;主要提供网络配置、变更和合规…

20年软件工程师的经验

软件工程师在做设计的时候&#xff0c;一定要有设计的思维&#xff0c;码农如果只是砌砖的&#xff0c;那么他的可替代性和技能能力并不高。前段时间看到一个设计师傅&#xff0c;在很狭小的空间内设计了非常非常不错的室内设计&#xff0c;利用了每一个可以利用的地方。如果我…

html超市代码,前端 CSS : 5# 纯 CSS 实现24小时超市

介绍原文链接感謝 comehope 大佬的 [前端每日实战]效果预览源代码地址代码解读1. html 结构命名规则使用了 BEM常规样式初始化* {margin: 0;padding: 0;box-sizing: border-box;}body {height: 100vh;overflow: hidden;}2. 街道背景街道背景分为两部分深蓝色的天空.street {hei…

送30块树莓派PICO 开发板!

大家好&#xff0c;今天是周日&#xff0c;给大家搞个小抽奖&#xff0c;送30块。嵌入式猛男必备&#xff0c;学嵌入式看『我要学嵌入式』&#xff0c;知识持久有力。点击关注&#xff0c;回复【1031】参与抽奖&#xff0c;免费送 10块 树莓派最新PICO开发板。学C语言看『写代码…