异步编程

这篇文章将介绍并发编程的异步模型。对于一些应用,异步模型可能比多线程模型要好一点。这篇文章中的大多数材料来自于Dave Peticola 对Twisted(一个异步的python框架)的介绍。

1 编程模型

我们将复习下两个熟悉(希望是熟悉的)的模型,以便和异步编程模型形成对比。通过图解形式,我们假设 一个程序包含了概念上不同的三个任务,当然这三个任务可以完成我们的程序。注意我并没有使用技术性相关的,需要特别处理的任务。

第一种模型是单线程同步模型,其如下图所示:

图1 单线程同步模型

这是最简单的编程模型,每个任务按照时间的顺序一个接一个的依次执行。如果所有的任务都是按照定义的顺序执行的,后续的任务在实现的过程中,可以假设前面的任务执行的过程中没有出现错误,而且他们所有的输出都是可以直接拿来使用的。

在图2中,我们比较了单线程同步模型和多线程模型(线程模型)。在多线程模型中,每一个任务由一个独立的线程控制。这些线程由操作系统管理,在一个多处理器或多核的环境中,真正的并发执行,也可能在一个单处理器中交替的执行。关键是,在线程模型种,程序执行的细节由操作系统操作,程序员可能简单的认为指令流是同时执行的。尽管例图很简单,但实际上多线程编程会变得很复杂,因为线程之间在执行的过程中需要相互同步。线程通信和同步是一个高级的编程话题,掌握它可能有些困难。

图2 多线程模型

一些程序使用多个进程实现并行性,而不是多个线程。虽然编程细节不同,在概念上是相同的模型,如图2所示

现在我们可以介绍异步模型了,如图3描述的那样:

图3异步模型

在异步模型中,各个任务是相互交错的,由一个单线程控制。这要比多线程模型简单,因为程序员知道,当一个任务执行时,另一个任务不会被被执行。虽然在一个单核处理器的系统中,一个多线程的程序也是一种交错的模式,但程序员在思考时应该像图2那样,而不是图3,避免程序移植到多处理器的系统中发生错误。但在单线程异步模型中,无论是在单处理器还是多处理器中,都是交错的执行的。

异步编程模型和多线程模型还有另外一个不同点。在多线程系统中,推迟一个任务的执行而去执行另一个任务大大的超出了程序员的控制。尽管,这个在操作系统的控制下,但程序员只能猜测一个任务被推迟,另一个任务来替代,可能在任何可能的时候发生。相比较,在异步模型中,一个任务会继续执行除非显示的放弃控制权给另一个任务。

2 动机

在一些方面,异步模型比多线程模型要简单,因为异步模型有简单的指令流,而且显示的让出控制权,而不是像多线程一样被随意的暂停。但是异步模型有它自己的复杂性,程序员必须组织每一个任务作为一系列的交替执行的小的步骤。如果一个任务使用了另一个任务的输出,那么所依赖的任务接收该任务的输入作为一连串的比特或块而不是所有的放在一起。

由于并没有实际的并行性,正如我们的图解所示,一个异步的程序可以和一个长时间执行的同步程序一样。在这样的一个条件下,一个异步的程序可以表现得比一个同步的程序更好,有时候也相反。这个条件就是我们的任务被强制的等待或阻塞,就如图4所示:

图4中断的同步程序

在图3中,灰色的部分代表一个任务被阻塞不能进行任何的步骤。为什么一个任务会被阻塞?一个频率较高的原因是它在等待I/O,从一个外部的设备传输数据。一个典型的CPU处理数据的速率要比磁盘和网络链接快好几个数量级并能一直维持这个能力。因此,一个同步的程序如果执行大量的I/O操作将会花费很多的时间被中断当磁盘或网络被挂起

注意图4中,一个阻塞的程序,看起来特别的像图3,一个异步的程序。这并不是一个巧合。异步模型背后的一个基本的思想是,一个异步程序,和一个正常阻塞的同步程序相比,除了可以去执行其他的任务外还可以继续的执行其它的步骤。所以一个异步的程序只会在没有任何任务执行步骤的时候才会被阻塞(一个异步的程序通常被称做一个非阻塞程序)。从一个任务切换到另一个任务,对应着一个任务刚好执行完或者是在某一点上被阻塞。当存在大量的阻塞任务时,一个异步的程序会比一个同步的程序表现得更好在花费的等待时间方面,当把一个大致相等的时间投入到实际工作中的单个任务上。

和一个同步的程序相比,一个异步的程序在以下的情况中表现得更好:

1存在大量的任务,大多数时间至少有一个任务在执行

2任务执行大量的I/O操作,导致一个同步的程序浪费大量的时间在阻塞当其他的任务可以执行时

3任务之间是相互独立的,各个任务很少需要内部的交流(因为一个任务等待另一个任务)

这些条件几乎完美地描述了一个典型的繁忙的网络服务器(如web服务器)在 C/S 环境中。每一个任务都代表一个客户端请求一接收请求并发送回应的I/O形式。实现一个网络服务器,异步编程模型是主要获选者,这就是为什么Twisted和Node.js,以及其他异步服务器库,近几年来越来越火。

你可能会问,为什么不使用更多的线程?如果一个线程阻塞在I/O操作,另一个线程是不是可以继续执行操作?然而,随着线程数的不断增加,你的服务器可能会开始出现性能问题。对于每一个新的线程,线程状态的创建和维护有一些内存开销。从异步模型中得到的另一个优势是可以避免上下文的切换。操作系统每一次切换线程时都要保存一些相关的寄存器,内存映射,栈指针,FPU上下文等信息,因此其他的线程在恢复的时候可以继续的执行。这方面的开销是相当大的。

3 事件驱动编程

我们已经看到,异步编程使我们能够花更少的时间等待,而只利用一个单一的线程。但我们如何能从中获益呢?考虑一个程序,等待从标准输入读取数据。

scanf函数将会被阻塞直到stdin文件描述符有数据到达。这就意味着线程不能执行任何其他的操作直到scanf函数返回。

大多数异步程序库遇到以上的程序会这样做:

1虽然阻塞在了scanf函数,继续执行后面的代码

2提供一块代码去执行当scanf函数真正发现stdin有数据到达。这个回调代码块将会被执行即使当scanf函数已经返回

如果你之前用过ajax,这个模式将会听起来很熟悉。这个模式被很多的GUI框架利用--“当这个按钮被点击的时候执行这个函数,但是不要一直等待着那个按钮被点击,这样会阻止我们做其他的事情”。如果你使用过Twisted 或者 Nodejs.你也会再次看到这种模式--提供一个回到函数去执行当某个事件在未来发生时。

libevent是一个异步事件通知库。虽然我们不会再这里使用libevent,但它是很多的异步程序框架的基础。libevent API提供了一种机制来执行一个回调函数,当一个特定事件发生在一个文件描述符上或超时。目前,libevent 支持 /dev/poll, kqueue, select, poll, and epoll,他们都有着不同的I/O通知的机制。libevent 提供了更高水平的抽象围绕着这些机制,根据具体的操作系统选择最好的一种。本篇文章将直接介绍select(),epoll(),但libevent可能在将来的异步编程中更加的有用。

4 select() 异步编程拯救者

编写一个网络服务器,传统的方式是使主线程阻塞在accept(),等待客户端的连接。当一个连接到来时,服务器产生一个新的线程或进程,子进程/线程负责处理这个连接,主服务继续服务新的可能到来的连接。

使用select(),不需要为每一个客户端准备一个线程/进程,通常只需一个线程,复用所有的请求,尽可能多的服务更多的客户端。所有使用select()一个主要的优点是你的程序可能只需要一个进程/线程去处理所有的请求。所以你的程序不需要共享内存或者是原始的同步机制去实现进程或线程间的通信。

select()以阻塞的形式工作直到在指定的文件描述符(可以代表一个确切的文件,一个管道(pip),或者是socket)上发生了某些事件.某些事件代表了什么?数据到达,可以往一个文件中写入数据,超时等等---你告诉select,你想通过什么被唤醒。

大多数基于select()的实现的服务器都严格围绕着由这些组成的事件循环:

1用文件描述符填充 fd_set 这样的一个结构体当我们关心该文件描述符有数据到来时

2用文件描述符填充fd_set结构体当你想得到通知可以向这个文件中写入数据

3 调用select()阻塞直到事件发生

4每一次select()返回时,检查任何一个文件描述符看是不是被唤醒的原因。如果是,为那个文件描述符提供服务无论你的服务器以何种特殊的方式。

5 一直执行这个流程

(结束,翻译原文:http://cs.brown.edu/courses/cs196-5/f12/handouts/async.pdf)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容