消息/事件, 同步/异步/协程, 并发/并行 协程与状态机 ——从python asyncio引发的集中学习

时间:2023-09-27 04:59
-->

我比较笨,只看用await asyncio.sleep(x)实现的例子,看再多,也还是不会。

已经在unity3d里用过coroutine了,也知道是“你执行一下,主动让出权限;我执行一下,主动让出权限”,但还是觉得迷迷糊糊,不清不楚的。

1起因:简单的分析模型世界

序列图里箭头一指,就表示消息和责任转移关系了。

静态数据+责任封装用类图,里的 方法,就表示 责任(消息+实现);

单个类的动态过程用 状态图, event-action 就够了。

没有异步/同步   message/callback这些区别

但一到设计层面,实现和落地的时候各种区别就来了。其实都是为了性能

所以,在各种需求变更泥潭  和 各种落地方案泥潭 之间,请一定先做好分析工作流

1  识别需求隐含的核心域概念。

2  把核心域概念之间的责任转移关系分析清楚

如果 业务和需求相当于战略,落地实现技巧和困难就是战术。夹在中间的分析/核心域领域建模就是战役。

理想中的系统复杂度,是个沙漏型的2头粗,中间细:

战略和战术都非常复杂多变,战役层要精简/清晰。

战略和战术都充满了动词,和动宾关系 :

战略上是系统之外 涉众 要系统做某事();

战术上是系统内部,系统要子模块、函数如何做()。

唯独战役层是用名词思考、描述问题的,也是强调分析模型的(《联合战役分析手册》)。

战役层要辨析出、归结到核心域的各种名词概念(概念模型、指标体系)的。

如果说软件工程是翻译工作:把涉众利益翻译成需求,把需求翻译成核心域概念,把核心域概念模型再翻译成代码。

那么战役/分析层是对艺术要求最高的部分,分析模式要做出战役法/指挥艺术的味道来(Operational Art)。

假设从业务场景到做好了核心域,知道是谁给谁发消息之前的业务场景。那么再进入设计。

2性能关注点:不让CPU因为等待外部网络IO而闲着

1对外,最低限度不能丢失使用者提交的申请(功能需求);此外,尽量不要让使用者干等着(易用性需求,用户体验),要么提交完了直接走人等通知;要么暂时挂起,等处理完了再运行。

2对内,尽量不让CPU闲着。

——大规模的web应用主要考虑的是1.  但我目前的关注点不是这个,所以我只要关注2就好了。

1.如果CPU是因为计算任务,已经可以吃满,那么根本不用考虑,简单粗暴多进程就OK;

2.延时最长的主要就是web请求,get post 这些。为等结果,让cpu原地傻等确实不明智。但如果是内部局域网切分的微服务,甚至是多个docker容器之间的虚拟网络,自己知道没太长等待时间,那么同步调用也没什么。requests还是够用了。(作者好像明确说近期不打算迎合asyncio,进行异步化)。

3 .不考虑对本地磁盘IO的优化,用同步读写够了(https://www.mareenoire.info/questions/41790750/writing-files-asynchronously)。因为现在是现代,有SSD!数据库也都用内存做了很多缓存工作,过度异步化会导致负效果。——由此可知:程序员不能只盯着自己手里的锤子——软件技术,要理解程序运行的外部环境:操作系统、硬件、网络环境。。。

3 消息/事件+回调/任务

1 message:通知、广播、报点……。管杀不管埋,管生不管养——情报、通信    最松的耦合关系。

2 event/callback  singal/slot:   本来是完整的处理流程,因为角色分工不同,导致流程上开了洞+很多补洞的材料:

server端、callback实现端: 知道怎么做,但不知何时做(补洞用的材料,馅料,必须要实);   ——作训  分队长

client端、event触发端:     会遇到不同事情,或者需要进行情况判断触发某些决策,也需要根据事情和后面的处理结果,继续触发某些决策,也就是说,既负责触发,又需要使用处理结果,只是不知道、不负责、不关心具体处理过程如何实现(留出很多洞的流程判断步骤,要考虑随时替换,插拔实现方式。要虚,要空灵)。——部队长、指挥员

3 task:封装之后(无入参)的有状态的可执行对象,封装过程、关注结果。状态就那么几种:等待执行,执行中,挂起,被取消,已完成。 触发task运行的层级,不关心具体结果是什么,任务在定义的时候自己有结束条件(完成标准)。  ——上帝视角,时间轴、甘特图上的一个条块

对已完成的任务,可以get_result()拿结果。也可以取消终止任务执行。

——这些设计实现的套路,显然都是来自于某些分析模型、核心域的。分析模式吃得透了,自然能感觉出、还原回,本来的分析模型和业务场景,再理解这些设计实现上的概念就容易多了。无非是角色分工不同,关注点分离之后不同的实现方式。

落地时使用哪种模型,要根据业务场景和核心域更像那种,挑着最像的那种用。就舒服了(用云的视角去看云,用风的视角去看风)。

4 同步/异步/协程:关键是 状态的表示与维护:

没想到,其实是和状态有关的!

说起web开发就常见到“无状态”,感觉很奇怪,为啥我经常要整天要琢磨状态机呢?

现在明白了,但是怎么可能无状态,只不过是你那里无状态了而已!转移到了别处,由别人维护了而已。

知乎上这个讲得最清楚:https://www.mareenoire.info/question/32218874

作者:Tim Shen
链接:https://www.mareenoire.info/question/32218874/answer/56255707
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

同步接口,异步接口和协程的关系了。

假设你有10个文件,你想把10个文件拼接起来模拟成一个大文件。但是你内存又没那么大,那只好读一点让用户消耗一点了。所以你得写个filecat这样的adaptor。

1 如果所有接口都用同步,用户是爽了,仿佛直接在读一个文件;你实现得很蛋疼,每当用户问你要数据的时候,你得先检查我现在读到第几个文件了,读完了没有,后面还有没有别的文件了,要维护一坨状态。

2 如果所有接口都用异步,你是爽了,直接把用户的回调挨个传到不同的文件读写调用里去;用户则蛋疼了。要是用户想读三行,停一停去做些别的(比如处理下这三行),是很麻烦的,因为你这个adaptor往用户端塞数据塞得根本停不下来,用户有什么想法都得往回调函数里塞。“我现在读到第几行了?如果小于三行,就先送到处理头三行的函数里去;如果等于三行,就要拿一下处理好的三行并送去不知道什么地方,如果大于三行,再干点别的。。。”所以用户得手动维护这些状态。

3 你会发现无论是同步还是异步,都有这种“手动维护状态”的问题。要想让adaptor和用户都开心,解法自然是协程。我现在就用代码来表示状态,执行一行就是状态的转移。但是两头的状态要交替变化,这边做三行,轮到那边做了;那边做完了,又回到这边来,这跳来跳去的,就是协程了

协程方便是因为“用代码来表示状态,而不是维护一坨数据结构来表示状态”,则客观得多。

——总结:协程更像下棋的回合:两个人交替按一个计时器,我走完按一下,你走完按一下。双方都要懂流程才玩得起来, 双方都分担了一部分当前状态和控制流进度的维护。

这才是协程的本质:分布式的控制流和状态保存。每个协程参与者async   都要知道何时用await让出控制流,如果某个家伙霸占了,整个控制流就卡死了。

所以,一处写了async,涉及到await 也都得是async(可以被await,可以包含await,不能返回值,只能返回其他coroutine)。unity也是类似的。这种传染性,一开始很不喜欢,但理解了控制流的分布式保存,就理解了。需要每个人都符合控制流,才能组装成1个超级复杂的控制流。

其实是控制流和状态保存 的分布式 VS 集中式的区别:

分布式:每个角色/模块都负责一小部分流程。没人负责描述整个完整流程(谁负责谁蛋疼)。

    1 用代码显式表达了每个控制流片段,但系统执行起来,当前整体状态就隐式表达了:状态表达的责任分解于无形了。

    2 整描述整个的控制流很复杂,不可能、也没必要;而单独看每件事的控制流又很简单。很多游戏里的动画音效就是这样。(同时看电影,发邮件,刷朋友圈也算)。

——有点类似 多agent仿真和官僚体系。虽然每个agent设计都不复杂,但只要agent数量堆上去,全局状态的数量,是指数上升的。没有简化系统整体复杂性,也因而具有某种客观性。

集中式:对每个重要角色/模块的当前状态,显式用一坨数据结构描述显式划分、有限数量的若干状态描述任意时刻的整体状态,状态机的划分与设计,体现了设计者主观视角和对事实的特定视角下的简化,严格说状态的数量,显然不如分布式排列组合之后的状态多。

——状态机是万能的。涉及多个参与者,不希望一个人出问题,卡崩全部控制流,希望保存、挂起状态,还是应该老实用状态机更保险

  因此,虽然做了棋,也在unity3d动画上用了,但想在核心流程上用协程,只是看起来很美好。在现在功力不足,比较棒槌的阶段,还是老实用状态机吧。(python的协程还不好保存。好像golang可以?)

5并发/并行

Concurrency is about dealing with lots of things at once.

Parallelism is about doing lots of things at once.

Concurrency叫并发,Parallelism叫并行。

并发是1个人同时做多件事(同时看电影,发邮件,刷朋友圈。遇到广告时间、网络延时就切换另外一个工作,每个事做几秒钟,脑子、眼睛始终不闲着);来回切换带来硬件的高利用率,每件事的效率不取决于自己,主要是因为网络延时。

并行是多个人同时各自做各自的事(自然是多件)(市场、产品、开发)。工作范围内自己说了算。专注、关注点分离、接口边界清晰带来工作结果的高质量、高效率

——和协程有交集的,就是1个人做多件事的并发,想完整描述整个人的控制流很复杂,也没必要,而单独看每件事的控制流又很简单。

然后再去读点实际用asyncio处理网络IO的代码示例,基本就理解了。

参考:

Easy parallel HTTP requests with Python and asyncio

Python黑魔法 --- 异步IO( asyncio) 协程

编程珠玑番外篇-Q 协程的历史,现在和未来

为什么觉得协程是趋势?Tim Shen的回答

-->