关于事件模型的一些见解

概述

事件处理模型, 也便是全异步事件处理模型。在之前, 对于那些同时执行多项任务, 但仍能响应用户交互的应用程序一般须要实施一种使用多进程(如linux的fork操做)或者多线程的操做。对于低并发的环境, 这样作无疑能避免进程因等待某个操做而出现"假死"现象。但对于更复杂的异步应用程序或者是要求高并发的环境, 就要使用事件模型来处理异步事件, 这样作有不少好处:javascript

  • 在高并发条件下响应用户时间更快;
  • 内存消耗下降, 能处理更多的用户请求;
  • 单进程, 在高并发下避免由于线程或进程之间切换带来的时间片消耗;
  • 有效下降死锁发生的频率

固然, 基于事件的处理模型也有其缺点:html

  • 事件处理器在处理的时候要尽可能的快, 不然就会阻塞主线程;
  • 进程会开辟额外的内存空间来维护请求状态;
  • 事件模型通常是单进程操做, 不能有效利用多核cpu的性能(能够经过建立子线程解决问题)

当前, 基于事件的处理模型被应用到了各类环境之中。譬如linux的epoll模块, javascript语言以及Nginx服务器等。java

本文地址: http://www.cnblogs.com/blackmanba/p/3676636.html或者http://forkme.info/about-event-loop/, 转载请注明源地址。linux

事件处理模型特色

<script type="text/javascript">
var callback = function () {
    alert('Hello world');
};

doucment.onclick = callback;
......
</script>

这就是实现事件处理模型的通常模式: 首先定义一个或多个异步事件, 每个事件绑定回调函数。主进程注册完事件后就继续执行, 只有当事件被触发时才会执行回调函数。放到例子当中就是当用户点击文档时会弹出窗口并显示 Hello world。服务器

综上: 基于事件模型的程序要具有如下结构, 如图:多线程

event loop

    1. 事件源: 就是事件的来源或者事件的产生者, 程序须要对事件源进行操做;
    1. 事件分离器: 获取事件池中的事件并根据事件源找到不一样的事件处理器按顺序进行处理;
    1. 事件处理器: 便是上文说到的回调函数, 用于处理具体业务;
    1. 事件循环: 周期性的获取事件源中的事件进行分发

事件模型实现原理

1. Event Loop

Event Loop是一个很重要的概念, 指的是计算机系统实现的一种运行方式, 主要用于等待并发送消息和事件。虽然在每一种实现方式中具体的实现方法可能不一样(例如Nginx事件模型和javascript事件模型), 可是都须要实现并维护Event Loop。
在计算机中, 每个运行的程序就叫作进程(process)。 通常状况下, 一个进程只能一次只能执行一个任务。若是想要执行多个任务, 不外乎如下三种解决方式:并发

  • 1 排队: 每次进程只能执行一个任务, 只好等前面的任务执行完了, 再执行后面的任务;
  • 2 新建进程: 对于每一次请求fork新的进程进行处理;
  • 3 新建线程: 对于每个请求新建线程进行处理

以上2和3方式分别对应多进程和多线程模型, 优劣在以前已经讲述。而事件处理模型正是基于上面第一种方式, 这样作能够避免没必要要的进程或线程间切换, 保证进程的高效运行。可是有一个问题, 一旦遇到大量任务或者是遇到一个耗时的操做, 进程就会被阻塞, 出现所谓的"假死"现象, 没法响应其余操做。异步

按照上面的运行模型, 若是某个任务很耗时,那么进程的运行大概是这个样子:async

Block

上图的绿色部分是程序运行时间, 红色部分是等待时间。这个进程在大部分时间都在等待其余操做, 这种运行方式称为"同步模式"(synchronous I/O)或"堵塞模式"(blocking I/O)。函数

若是采用多进程或者多线程, 极可能就是下面这种状况:

Block

上图代表, 比起单进程, 多进程耗费成倍的系统资源并闲置等待, 这显然不合理。

Event Loop就是为了解决单进程阻塞问题而提出来的一种合理方案。以javascript做为例子, 在程序中设置一个主进程和若干个子线程(以1个子线程为例)。主进程负责程序自己的运行并处理"非阻塞"操做; 子线程负责与其余进程(主要是I/O操做等耗时进程)的通讯, 这个线程就被称为"Event Loop线程"。

non block

上图橙色表示空闲时间。每当遇到I/O操做等耗时操做时, 主进程就让Event Loop线程去通知相应的I/O程序并注册相应的事件和设置回调函数, 而后主线程接着日后运行, 因此不出现红色的阻塞时间, 等到I/O操做完成, Event Loop线程再把结果返回主进程, 主进程调用实现设定的回调函数完成任务(注意: 回调函数不能执行太多的耗时操做, 由于此时主进程是处于阻塞状态的)。这种运行方式称为"异步模式"(asynchronous I/O)或"非堵塞模式"(non-blocking mode)。这也是事件处理模型的内部实现机制。

2. 上下文(Context)

(1)上下文含义

在计算机中, 上下文表明着不少种含义。在基于事件的模型中,上下文表示什么?简单的讲, 以javascript为例, 就是在主进程注册事件后将回调函数做用域内的变量保存成一个对象保存起来, 这个对象就叫作上下文对象。每个事件的回调函数都包含着本身的上下文对象, 当对应事件被调用并执行回调函数时, 函数能够直接经过上下文对象得到做用域内保存变量的值, 这个操做对开发者是透明的, 开发着只须要直接写对象名称就能够了。

(2)为何要定义上下文

在基于事件的模型中, 上下文是必须存在的, 为何要定义上下文这个概念呢?由于基于事件的模型都是基于异步机制的。以javascript为例, 每次主进程执行到结尾的时候就会释放其做用域内的全部变量。换句话说, 无论事件有没有被触发, 主进程执行完全部的逻辑以后就会销毁全部的变量。若是没有上下文的话, 当用户触发事件执行回调函数时, 就会没法找到其做用域内的变量, 使用上下文就是为了让每个特定的回调函数能访问到属于本身做用域内的变量的值。

权衡线程数

在实现一个基于事件模型的应用的过程当中, 有一个问题是必须考虑到的, 那就是主进程究竟要建立多少子线程才比较合理, 也就是说Event Loop何时须要建立。若是建立的子线程太多或是太少, 会出现如下问题:

  • 1 线程建立太多: 线程越多, 线程间的切换就越频繁, cpu就会消耗更多的时间在线程间的切换上。致使实际处理逻辑执行时间太短;
  • 2 线程建立太少: 线程越少, 就会有越多的阻塞操做集中在同一个线程中, 会影响同一线程内其余阻塞操做的执行

子线程的数目主要要根据应用处理的业务类型, 具体机器的内存和cpu等因素决定, 并无一致的规定。如下作法是比较好的一种实践:

  • 对于大文件上传, 下载等耗时比较长的操做建议是每次建立新的线程来处理;
  • 根据业务类型, 用户比较急需的阻塞操做能够独开线程处理;
  • 对于耗时较短并且是不固定时间的操做, 好比点击事件等, 能够用一个Event Loop来放置这些阻塞事件

总结

对于高并发, 多请求的事件, 传统的多线程和多进程的方法已经难以应付这些状况。而基于事件的模型因为采用的是异步的方式, 经过相似于中断上下文的操做, 能得到很好的并发性和高性能。而且能避免以前模型出现的一些棘手问题, 好比死锁问题。随着应用复杂度飞不断提升, 相信会有更多的应用将会采用基于事件的模型来应对这些状况。

相关文章
相关标签/搜索