有关异步编程框架的讨论

2016-08-17  本文已影响1025人  泡沫与周期_白羊Jerry

前言

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

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

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

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

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

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

#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,返回的处理控制逻辑被掌握在引擎手里。因此可以通过很多技术和调整来提高程序效率。

总结

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

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

上一篇 下一篇

猜你喜欢

热点阅读