纵横捭阖C 之从异步谈起
2008-02-23 05:24:40来源:互联网 阅读 ()
一般来说,简单的异步(Asynchronous)调用是这样一种调用方式:发起者请求一个异步调用,通知执行者,然后处理其他工作,在某一个同步点等待执行者的完成;执行者执行调用的实际操作,完成后通知发起者。能够看出,在异步调用中有两种角色:发起者和执行者,他们都是能主动运行的对象,我们称为主动对象,同时更有一个同步点,主动对象在同步点协调同步。在本文中,我们讨论主要是通用电脑、多进程多线程的分时操作系统上的异步调用。在操作系统的角度上来看,主动对象包括了进程、线程和硬件上的IC等,至于中断,能够看作总是在某个进程或线程的上下文借用一下CPU。而同步操作能够通过操作系统得各种同步机制:互斥锁,信号灯等等来完成。
我们能够先看看异步调用在Windows(本文中一般不加指出的话,都是特指NT/2000)读写文档中的应用。Windows中的ReadFile和WriteFile都提供了异步的接口。以ReadFile为例,
BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
假如最后一个参数lpOverlapped不为NULL,并且文档以FILE_FLAG_OVERLAPPED标志打开,那么这个调用就是异步的:ReadFile会立即返回,假如操作没有立即完成(返回FALSE并且GetLastError()返回ERROR_IO_PENDING),那么调用者能够在某个时刻通过WaitForSingleObject等函数来等待中的hEvent来等待操作完成(可能已完成)进行同步,当操作完成以后,能够调用GetOverlappedResult者获得操作的结果,比如是否成功,读取了多少字节等等。这里的发起者就是应用程式,而执行者就是操作系统本身,至于执行者是怎么执行的,我们会在后面的篇幅讨论。而两者的同步就是通过一个Windows Event来完成。
把这个异步调用的过程再抽象和扩展一些,我们能够把异步调用需要解决的问题归结为两个:一个是执行的动力,另一个是主动对象的调度。简单来说,前者是各个主动对象(线程、进程或一些代码)是如何获得CPU,后者是各个主动对象如何协同工作,确保操作的流程是协调正确的。一般来说,进程和线程都能够由操作系统直接调度而获得CPU,而更细粒度的,比如一些代码的调度,往往就需要一个更复杂的模型(比如在操作系统内部的实现,这时候线程的粒度太粗了)。而主动对象的调度,当参和者较少的时候,能够通过基本的同步机制来完成,在更复杂的情况下,可能通过一个schedule机制来做会更实际一些。
动力和调度
如前所述,异步调用主要需要解决两个问题:执行的动力和执行的调度。最普遍的情况就是,一个主导流程的调用者进程(线程),一个或多个工作者进程(线程),通过操作系统提供的同步机制来完成异步调用。这个同步机制在扩展化的情形下,是个或多个栅栏Barrier,对应于每个同步的执行点。任何需要在这个执行点同步的主动对象会等待相应的Barrier,直到任何对象都完成。在一些简化的情形,比如说工作者并不关心调用者的同步,那么这个Barrier能够简化成信号灯,在只有一个工作者的情况下,能够简化成一个Windows事件Event或条件变量 Condition Variable。
现在来考虑复杂的情形。假设我们用一些线程来协作完成一项工作,各个线程的执行之间有先后顺序上的限制,而操作系统就是这项工作的调度者,负责在适当的时候调度适当的线程来获得CPU。显然,并发执行中的一个线程对于另外一个线程来说,本质上就是异步的,假如他们之间有调用关系,那也就是个异步调用。而操作系统能够通过基本的同步机制使得合适的线程才被调度,其他未完成的线程则处于等待状态。举例说,我们有4个线程A,B,C,D来完成一项工作,其中的顺序限制是A>B;C>D,“>”表示左边的线程完成必须先于右边的线程执行,而“;”表示两个线程能够同时进行。同时假设B的一个操作需要调用C来完成,显而易见,这时候这个操作就是个异步调用。我们能够在每个“>”的位置设定一个同步点,然后通过一个信号灯来完成同步。线程B,C等待第一个信号灯,而D会等待第二个信号灯。这个例子的动力和调度都是通过操作系统的基本机制(线程调度和同步机制)来完成。
把这个过程抽象一下,能够描述为:若干个主动对象(包括代码)协调来完成一项工作,通过一个调度器来调度,实际上,这个调度器可能只是一些调度规则。显然,进程或线程只要被调度就能获得CPU,所以我们主要考虑代码(比如一个函数)怎么样才能获得执行。用工作者线程来调用这个函数显然是直观和通用的一个方案。事实上,在用户空间(user space)或用户态(user mode),这个方法是很常用的。而在内核态(kernel mode),则能够通过中断来获得CPU,这个通过注册IDT入口和触发软中断就能够完成。硬件设备上的IC是另一个动力之源。而主动对象的调度,最基本的也是前面说的各种同步机制。另一个常用的机制就是回调函数,需要注意的是,回调函数一般会发生在跟调用者不相同的上下文,比如说同一个进程的不同线程,这个差别会带来一些限制。假如需要回调发生在调用者的进程(线程)上下文,则需要一些类似Unix下的signal或Windows下的APC机制,这一点我们在后面会有所阐述。那么在回调函数里面一般作些什么事情呢?最常用的,跟同步机制结合在一起,当然就是释放一个互斥锁,信号灯或Windows Event(Unix的条件变量)等等,从而使得等待同步的其他对象能够得到调度而重新执行,实际上,也能够看作是通知调度器(操作系统)某些主动对象(等待同步的)能够重新被调度了,从而调度器重新调度。但是对于另外一些调度器,在这个过程中可能无需同步对象的参和。在一些极端一些的例子里,调度甚至不需要严格有序的。
在实际应用中,根据环境的限制,异步调用的动力和调度的实现方式能够有很大差别。我们会在后面的例子里加以说明。 操作系统中的异步:Windows的异步I/O。
Windows NT/2000是个抢占式的分时操作系统。Windows的调度单位是线程,他的 I/O架构是完全异步的,也就是说同步的I/O实际上都基于异步I/O来完成。一个用户态的线程请求一个I/O的时候会导致一个运行状态从user mode到kernel mode的转变(操作系统把内核映射到每个进程的2G-4G的地址上,对于每个进程都是相同的)。这个过程是通过中断调用内核输出的一些System Service来完成,比如说ReadFile实际上会执行NtReadFile(ZwReadFile),需要注意的是,运行上下文仍然是当前线程。NtReadFile的实现则基于Windows内核的异步I/O框架,在I/O Manager的协助下完成。需要指出的是,I/O Manager只是由若干API构成的一个抽象概念,并没有一个真正的I/O Manager线程在运行。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇: Boost源码剖析:C 泛型函数指针类
下一篇: C STL编程轻松入门
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash