[转帖]有关异步编程框架的讨论

有关异步编程框架的讨论

https://www.jianshu.com/p/c4e63927ead2

前言

从python的twisted,到之后Java的NIO,Netty,以及Nodejs带着底层libuv的横空出世,以及现在热议的Golang。异步/多线程/协程编程已经成为了一个耳熟能详的编程范式,它从理论上可以最大化cpu利用率和I/O资源,在计算机核数越来越多,服务器端性能要求越来越高的环境下,熟练掌握异步编程模式已经成为了基本要求。

本文我们来梳理一下异步编程到底是怎么回事。

计算机本身即是同步也是异步

  • 波粒二象性
    一台裸机,配上主板,内存,CPU。这是同步模型还是异步模型?都是,说异步,因为CPU被内部的时钟驱动,每次时钟信号会驱使CPU累进当前读取地址,从内存读出新的内容,接着CPU计算和输出。从这一点看,CPU是被皮鞭不断抽打着前进的异步模式。
    然而,它又是同步的,因为皮鞭挥动速度实在太快,i7 CPU频率到了4Ghz,每秒挥动4,000,000,000次。指令如流水一样的按序执行,在逻辑上我们通常把它当做一个同步模型来理解。
    是不是有点像光的波粒二象性?
  • 中断
    中断是计算机跟外界交互的基础。如果CPU只能累进地址,那么所有的编程模型将都会是同步---如同穿孔纸带那样顺次排列code执行。

    穿孔纸带

    中断是CPU提供的一种机制,当某固定针脚上的信号发生变化时,CPU跳跃执行另外一套指令。有了这套机制,我们才可能使用各种I/O(硬盘,键盘,显示器)设备。
    比如说,当我们从键盘上敲入一个字符"A",系统会受到一个中断,CPU就跳到键盘的中断处理程序上,读取了输入内容"A",然后将这个事件继续传递给显示程序,显示程序就在屏幕上打出了一个"A"。

  • 操作系统是一个异步系统
    操作系统为了方便人操作机器而产生的,它的使命就是接受各种I/O操作产生各种输出,它天然就是一个异步模型。从某种程度上来说,操作系统就像一个大型的中断处理库。

程序是同步模型还是异步模型

  • 程序本质是同步的
    虽然操作系统是异步,但是单个程序是同步。。。我们的程序交给操作系统执行时,操作系统会构建堆栈,页表,然后把代码复制到内存中,然后用CPU顺序执行,从这个角度讲,跟打在穿孔纸带上没啥区别。
  • 系统调用
    但是我们也可以调用I/O等基于中断机制的操作?那是你以为,其实程序本身是无权调用I/O程序的,只能向操作系统发出请求,说让我读一下网卡?操作系统收到请求后,就会停止当前程序,然后自己去读取网卡,等网卡向操作系统发出中断表示数据准备完毕后,操作系统会把数据转移到程序的某个位置,然后再“唤醒”程序。在网卡收到读取请求和发出中断这之间的时间,原本的程序会被“冻结”,操作系统会调度其它的程序占用CPU继续执行。
    实际上来看,程序是被异步执行。但程序本身的感觉却是同步,当它发出I/O请求的那一刻,它的“时间”被凝固了,直到操作系统重新打个响指,程序反应过来时,数据已经被放在眼前,仿佛魔术一般。由于不可感知,所以我们写程序实际只能是基于同步模型。
  • 多进程/线程-大并发高性能计算的要求
    操作系统这么做是为了平衡CPU和I/O资源,这样一个进程在等待I/O时,另一个进程可以利用CPU进行计算。从它的角度看这么做无可厚非,但是现在很多服务器就是为了单一功能而存在的,它只需要最大化某一个程序的CPU,I/O利用率,这种被迫的让出并不是它期待的结果。
    因此有一个办法就是用进程/线程“淹没”操作系统,比如Apache的webserver,每一个请求另外启动一个进程/线程去处理。于是挂在I/O上的是webserver的listen进程/线程,占用CPU的则是webserver的child进程/线程,最后提高了系统的吞吐率。

异步-更高性能和更大并发的结果

上述方案够吗?不够,因为人总是贪心的,交给操作系统去做切换是非常耗时的操作,另外大量的进程/线程的创建销毁也是不小的开销。程序开发者想直接控制这些过程,而不是交给笨重的操作系统。那怎么办呢?

  • 重新打造一个事件驱动引擎
    程序本来是被CPU驱动的,当有I/O请求时这个驱动没了---CPU被抽走执行其它程序去了。那我现在需要操作系统全力执行我这个程序,怎么办?
    无论python twisted, Java Netty, Nodejs libuv,所做的事情是一样的,做一个自己的事件驱动引擎:
#Python twisted
class _SignalReactorMixin(object):
  def startRunning(self, installSignalHandlers=True):
     self._installSignalHandlers = installSignalHandlers
     ReactorBase.startRunning(self) def run(self, installSignalHandlers=True):
     self.startRunning(installSignalHandlers=installSignalHandlers)
     self.mainLoop() 

  def mainLoop(self):
     while self._started:
        try:
             while self._started:
            # Advance simulation time in delayed event
            # processors.
             self.runUntilCurrent()
             t2 = self.timeout()
             t = self.running and t2
             self.doIteration(t)
        except:
             log.msg("Unexpected error in main loop.")
             log.err()
        else: log.msg(‘Main loop terminated.‘)

Nodejs使用Libuv库作为主线程引擎.

Java Netty框架同样自己写了一个EventLoop

//Java Netty
protected void run() {
        for (;;) {
            try {
                int strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                switch (strategy) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.SELECT:
                        strategy = epollWait(WAKEN_UP_UPDATER.getAndSet(this, 0) == 1);
                        if (wakenUp == 1) {
                            Native.eventFdWrite(eventFd.intValue(), 1L);
                        }
                    default:
                        // fallthrough
                }

                final int ioRatio = this.ioRatio;
                if (ioRatio == 100) {
                    if (strategy > 0) {
                        processReady(events, strategy);
                    }
                    runAllTasks();
                } else {
                    final long ioStartTime = System.nanoTime();

                    if (strategy > 0) {
                        processReady(events, strategy);
                    }

                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
                if (allowGrowing && strategy == events.length()) {
                    //increase the size of the array as we needed the whole space for the events
                    events.increase();
                }
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        break;
                    }
                }
            } catch (Throwable t) {
                logger.warn("Unexpected exception in the selector loop.", t);

                // Prevent possible consecutive immediate failures that lead to
                // excessive CPU consumption.
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // Ignore.
                }
            }
        }
    }

这三个框架的事件引擎实现从本质来说是一样的,就像CPU通过内置的晶振来驱动一样,引擎通过设置短超时时长,高频率的调用select/poll/epoll。I/O事件或者超时返回成为了引擎的驱动力。每次调用返回时,就检查对应的I/O端口上是否有事件,如果有则执行绑定的回调,或者另外开线程处理回调,另外它们还会单独实现一个事件队列用来存储非I/O绑定事件,在每次轮询返回时也会检查该事件队列并处理到期的事件。
假设设置的超时时长是50ms,当任何I/O操作都没有的时候,事件引擎就退化成一个50ms执行一次检查的事件队列。

轮询实质上是同步操作,不过也没办法,因为前面讲过了,程序本身就是同步模型。在低并发量的时候,这种异步框架效率还会低于同步框架,因为浪费了大量CPU时间在轮询等待上,但是在高并发量的时候,每次轮询都会立即返回,几乎变成一个纯异步模型,但不同于直接调用系统API,返回的处理控制逻辑被掌握在引擎手里。因此可以通过很多技术和调整来提高程序效率。

  • Golang
    值得单独一提的是Golang,Golang牛逼哄哄的自己实现了一个调度器!对,你没看错,它自己写了一个调度器。
    因为即使是线程,也有很多context,切换起来也非常耗时。Golang为了给使用者提供便捷的Goroutine模型,干脆自己写了个调度器,每一个Goroutine有自己单独的栈,instruction pointer等,这些信息都保存在堆上。当某个Goroutine被调度执行时,就从堆上恢复其context,调用实际的系统物理线程执行。
    在这种模式下,实际物理线程不需要频繁切换了,它实际执行的是一个routine队列,队列的调度器每次选出Goroutine交付给线程执行,Goroutine是超轻量级的异步并发执行单元。
    当Golang中某一个Goroutine调用系统I/O时,调度器会调用对应I/O的non blocking版本,然后将该Goroutine转入等待状态,并切换另一个Goroutine执行,同时也通过一个轮询机制等该I/O返回时唤醒对应Goroutine,从而实现CPU和I/O资源的高效利用。

总结

其实从某种程度来说,异步框架是程序试图跳出操作系统界定的同步模型,重新虚拟出一套执行机制,让框架的使用者看起来像一个异步模型。另外通过把很多依赖操作系统实现的笨重功能换到程序内部使用更轻量级的实现。

其实怎么看操作系统都像是累赘了。。。也许有一个完美的解决方案是直接写一个专门的异步式操作系统,可以实现性能的最大化利用。

原文地址:https://www.cnblogs.com/jinanxiaolaohu/p/11691753.html

时间: 2024-10-12 07:32:23

[转帖]有关异步编程框架的讨论的相关文章

<史上最强>深入理解 Python 异步编程(上)

前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知道如何使用 Tornado.Twisted.Gevent 这类异步框架上,出现各种古怪的问题难以解决.而且使用了异步框架的部分同学,由于用法不对,感觉它并没牛逼到哪里去,所以很多同学做 Web 后端服务时还是采用 Flask.Django等传统的非异步框架. 从上两届 PyCon 技术大会看来,异步编程已经成了 Python 生态下一阶段的主旋律.如新兴的 Go.Rust.

深入理解 Python 异步编程(上)

http://python.jobbole.com/88291/ 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知道如何使用 Tornado.Twisted.Gevent 这类异步框架上,出现各种古怪的问题难以解决.而且使用了异步框架的部分同学,由于用法不对,感觉它并没牛逼到哪里去,所以很多同学做 Web 后端服务时还是采用 Flask.Django等传统的非异步框架. 从上两届 PyCon 技术大会看来,异步编程已经成

这篇文章讲得精彩-深入理解 Python 异步编程(上)!

可惜,二和三现在还没有出来~ ~~~~~~~~~~~~~~~~~~~~~~~~~ http://python.jobbole.com/88291/ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 在Python 3.3 引入yield from新语法之后,就不再推荐用yield去做协程.全都使用yield from由于其双向通道的功能,可以让我们在协程间随心所欲地传递数据. 4.5.3 yield from改进协程总结 用yield from改进基于生成器的协程,代码抽象程度更高.使业务

Python Twisted网络编程框架与异步编程入门教程

原作出处:twisted-intro-cn 作者:Dave 译者:杨晓伟 luocheng likebeta 转载声明:版权归原作出处所有,转载只为让更多人看到这部优秀作品合集,如果侵权,请留言告知 感 谢:感谢 杨晓伟 luocheng likebeta 为国内Twisted Coder做的里程碑级贡献 其 它:能访问到Github的童鞋,请访问出处链接.因为出处排版相当棒! 1.Twisted理论基础 2.异步编程初探与reactor模式 3.初次认识Twisted 4.由twisted支持

异步编程和线程的使用(.NET 4.5 )

C#:异步编程和线程的使用(.NET 4.5 ) 异步编程和线程处理是并发或并行编程非常重要的功能特征.为了实现异步编程,可使用线程也可以不用.将异步与线程同时讲,将有助于我们更好的理解它们的特征. 本文中涉及关键知识点 1. 异步编程 2. 线程的使用 3. 基于任务的异步模式 4. 并行编程 5. 总结 异步编程 什么是异步操作?异步操作是指某些操作能够独立运行,不依赖主流程或主其他处理流程.通常情况下,C#程序从Main方法开始,当Main方法返回时结束.所有的操作都是按顺序执行的.执行操

使用异步编程

转发至:http://www.ituring.com.cn/article/130823 导言 现代的应用程序面临着诸多的挑战,如何构建具有可伸缩性和高性能的应用成为越来越多软件开发者思考的问题.随着应用规模的不断增大,业务复杂性的增长以及实时处理需求的增加,开发者不断尝试榨取硬件资源.优化. 在不断的探索中,出现了很多简化场景的工具,比如提供可伸缩计算资源的Amazon S3.Windows Azure,针对大数据的数据挖掘工具MapReduce,各种CDN服务,云存储服务等等.还有很多的工程

NodeJS的异步编程风格

NodeJS的异步编程风格 http://www.infoq.com/cn/news/2011/09/nodejs-async-code NodeJS运行环境因其支持Javascript语言和异步编程受到开发社区越来越多的关注.从GitHub上的访问量来看,NodeJS项目的关注度在最近几个月已经超过了Ruby及RoR.作为一个新鲜的平台,开发人员开始尝试去接触并运用于实际工作中,比如LinkedIn.Yammer.GitHub.淘宝等企业已经在生产环境中部署了NodeJS应用.不过,在学习No

C#:异步编程和线程的使用(.NET 4.5 )

C#:异步编程和线程的使用(.NET 4.5 ) 异步编程和线程处理是并发或并行编程非常重要的功能特征.为了实现异步编程,可使用线程也可以不用.将异步与线程同时讲,将有助于我们更好的理解它们的特征. 本文中涉及关键知识点 1. 异步编程 2. 线程的使用 3. 基于任务的异步模式 4. 并行编程 5. 总结 异步编程 什么是异步操作?异步操作是指某些操作能够独立运行,不依赖主流程或主其他处理流程.通常情况下,C#程序从Main方法开始,当Main方法返回时结束.所有的操作都是按顺序执行的.执行操

Python开源异步并发框架

Python开源异步并发框架的未来 2014年3月30日,由全球最大的中文IT社区CSDN主办的“开源技术大会·2014” (Open Source Technology Conference 2014,简称OSTC 2014)在北京丽亭华苑酒店召开. 本次大会以“启蒙·开源”(Open Mind, Open Source)为主题,邀请到了来自全国各地的30多位开源业界资深人士发表主题演讲,数十个开源社区现场参与,到场的开源软件开发者.贡献者和开源爱好 者总人数超过500人.作为一场“接地气”的