《Win32多线程程序设计》之异步IO

本章描述如何使用 overlapped I/O(也就是 asynchronous I/O)?

某些时候 overlapped I/O 可以取代多线程的功用。然而, overlapped I/O 加上completion ports,常被设计为多线程处理,以便在一个“受制于 I/O 的程序”(所谓 I/O bound 程序)中获得高效率。

这一章介绍一个你可能不想使用多线程的场合。许多应用程序,例如终端机模拟程序, 都需要在同一时间处理对一个以上的文件的读写操作。利用 Win32 所谓的 overlapped I/O 特性,你就可以让所有这些 I/O 操作并行处理,并且当任何一个 I/O 完成时,你的程序会收到一个通告。其他操作系统把这个特性称为 nonblocking I/O 或 asynchronous I/O。

什么是overlappedI/O?

overlapped I/O 是 Win32 的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O 进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来完成 overlapped I/O。你可以获得线程的所有利益,而不需付出什么痛苦代价。

需明确如下几个应用:
- 激发的文件 handles
- 激发的 event 对象
- 异步过程调用(Asynchronous Procedure Calls, APCs)
- I/O completion ports(!!!最重要!!!)
其中以 I/O com pletion ports 特别显得重要,因为它们是唯一适用于高负载服务器(必须同时维护许多连接线路)的一个技术。 Completion ports 利用一些线程,帮助平衡由“I/O 请求”所引起的负载。这样的架构特别适合用在SMP 系统(译注:支持多个 CPU 的操作系统)中产生所谓的 “scalable”服务器
所谓 scalable 系统,是指能够藉着增加 RAM 或磁盘空间或CPU个数而提升应用程序效能的一种系统。

Win32 文件操作函数
a) 基本IO函数
- CreateFile():打开各种资源,如文件、串口和并口、Named pipes、Console
其形参第6个参数 dwFlagsAndAttributes 是使用 overlapped I/O 的关键。当设为FILE_ FLAG_OVERLAPPED 时,则对该文件的每个操作都将是overlapped,可以同一时间读写文件,同时在ReadFile() 和WriteFile()中的第5个参数需提供其指针LPOVERLAPPED lpOverlapped。
- ReadFile()
- WriteFile()
- CloseHandle():关闭

b) OVERLAPPED 结构

typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;

由于 OVERLAPPED 结构的生命期超越 ReadFile() 和 WriteFile() 函数,所以把这个结构放在一个安全的地方是很重要的事情。通常局部变量并不是一个安全的地方,因为它会很快就越过了生存范围(out of scope)。最安全的地方就是 heap

被激发的文件Handles

最简单的overlapped IO类型是:
首先你以 FILE_ FLAG_OVERLAPPED 告诉 Win32 说你不要使用默认的同步 I/O 。然后,你设立一个 OVERLAPPED 结构,其中内含“I/O 请求”的所有必要参数,并以此识别这个“I/O 请求”,直到它完成为止。接下来,调用 ReadFile() 并以 OVERLAPPED 结构的地址作为最后一个参数。这时候,理论上, Win32 会在后台处理你的请求。你的程序可以放心地继续处理其他事情。

此处的后台处理需明确,有可能马上处理,文件操作在ReadFile() 返回之前完成,此时ReadFile() 返回TRUE,文件handle处于激发状态。但有可能是放到队列中等待执行,此时ReadFile()返回FALSE,为此必须调用GetLastError() 并确定它传回 ERROR_IO_PENDING ,那意味着“overlapped I/O 请求”被放进队列之中等待执行。 GetLastError() 也可能传回其他的值,例如 ERROR_ HANDLE_EOF,那就真正代表一个错误了。

如果你需要等待 overlapped I/O 的执行结果,作为WaitForMultipleObjects()的一部分 , 请在handle 数组中加上这个文件handle。文件 handle 是一个核心对象,一旦操作完毕即被激发。当你完成操作之后,请调用 GetOverlappedResult() 以确定结果如何。

其实也可以不用Wait…API,因为GetOverlappedResult() 就可以用来等待 overlapped 操作的完成。

被激发的 Event 对象

引入原因
以文件 handle 作为激发机制,有一个明显的限制,那就是没办法说出到底是哪一个 overlapped 操作完成了。如果每个文件 handle 只有一个操作等待决定,上述问题其实并不成为问题。但是如我稍早所说,系统有可能同时接受数个操作,而它们都使用同一个文件 handle 。于是很明显地,为每一个可能正在进行中的 overlapped 操作调用 GetOverlappedResult(),并不是很有效率的做法。

OVERLAPPED 结构中的最后一个栏位,是一个 event handle。如果你使用文件 handle 作为激发对象,那么此栏位可为 NULL 。当这个栏位被设定为一个 event 对象时,系统核心会在 overlapped 操作完成的时候,自动将此event 对象给激发起来。由于每一个 overlapped 操作都有它自己独一无二的OVERLAPPED 结构,所以每一个结构都有它自己独一无二的一个 event 对象,用以代表该操作。event 对象必须是手动重置(manual-reset)而非自动重置。
优点好处
使用 event 对象搭配 overlapped I/O,你就可以对同一个文件发出多个读取操作和多个写入操作,每一个操作有自己的 event 对象;然后再调用WaitForMultipleObjects() 来等待其中之一(或全部)完成。
实际应用
有多少IO操作就需要创建多少个手动重置的event handle,循环调用ReadFile()函数,判断调用结果,如为FALSE,则判断GetLastError(),若是ERROR_IO_PENDING,说明此IO操作等待执行,若是ERROR_INVALID_USER_BUFFER 、ERROR_NOT_ENOUGH_QUOTA、 ERROR_NOT_ENOUGH_MEMORY,则表明没有足够资源来处理IO请求,等。

异步过程调用(Asynchronous Procedure Calls, APCs)

引入:
使用 overlapped I/O 并搭配 event 对象,会产生两个基础性问题。
一使用 WaitForMultipleObjects(),等待最多达 MAXIMUM_WAIT_OBJECTS 个对象。如果你要等待 64 个以上的对象,就会出问题。所以即使在一个CS环境中,你也只能同时拥有 64 个连接点。
二你必须不断根据“哪一个 handle 被激发”而计算如何反应。你必须有一个分派表格(dispatch table )和 WaitForMultipleObjects() 的handles 数组结合起来。
这两个问题用APC解决。但很少用,因Windows基本能够用。

用法:
只要使用“Ex” 版的 ReadFile() 和 WriteFile() ,你就可以调用APC机制。一个 callback 函数地址。当一个 overlapped I/O 完成时,系统应该调用该 callback 函数。这个 callback 函数被称为 I/O completion routine ,因为系统是在某一个特别的
overlapped I/O 操作完成之后调用它。

callback函数:
C++中没有his指针的成员函数方可。

VOID WINAPI FileIOCompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransferred,
LPOVERLAPPED lpOverlapped
};

注:只有当程序处于 “alertable” 状态下时, APCs 才会被调用
如果线程因为以下五个函数而处于等待状态,而其 “alertable” 标记被设为 TRUE ,则该线程就是处于 “alertable” 状态:

- SleepEx()
- WaitForSingleObjectEx()
- WaitForMultipleObjectsEx()
- MsgWaitForMultipleObjectsEx()
- SignalObjectAndWait()

I/O Completion Ports

引入:
APCs虽解决了俩问题,但仍有两个缺点。
一 某些IO API,不支持APCs.
二 只有发出overlapped请求的那个线程才能提供cb函数。scalable系统最好任何线程都能服务events.
故有了IO completion ports

优点:
靠着“一大堆线程服务一大堆 events ”的性质, completion ports 比较容易建立起 “scalable” 服务器。
Completion ports 解决了我们截至目前看到的所有问题:
- 与 WaitForMultipleObjects() 不同,这里不限制 handles 的个数
- I/O completion ports 允许一个线程将一个请求暂时保存下来,而由另一个线程为它做实际服务。
- I/O completion ports 默默支持 scalable 架构

I/O completion port 是一个机制,用来管理一堆线程如何为 completed overlapped I/O requests 服务。其所提供的功能甚至可以跨越多个 CPUs 。一般而言你希望让所有的 CPUs 都忙碌,所以默认情况下并行处理的线程个数就是 CPUs 的个数。Completion port 可以自动补偿成长中的服务器,适合应用于沉重的负担。

用法:

目前,一个 overlapped I/O 操作的完成,可以藉由文件 handle 的激发、 event 对象的激发,或是 APC ,通知某个对此感兴趣的线程。 I/O completion port 的运作方式截然不同。
我们搜寻的目标是任何一个能够为“completed overlapped I/O request”服务的线程。如果使用其他种类的 overlapped I/O 机制,你必须锁定,并且由一个特定的线程为一个特定的“I/O 请求”服务。 I/O completion port 允许你将“启动 overlapped 请求的线程”和“提供服务的线程”拆伙。

  1. 产生一个 I/O completion port。
    CreateIoCompletionPort通常被访问两次,第一次filehandle为 INVALID_HANDLE_VALUE。
  2. 让它和一个文件 handle 产生关联。
    CreateIoCompletionPort第二次调用,文件需先以FILE_FLAG_OVERLAPPED 开启。
  3. 产生一堆线程。
    在该 port 上等待的那些线程,合理的线程个数应该是 CPU 个数的两倍再加2
    在port上等待的线程是以先进后出(first in last out, FILO)的次序提供服务。
    目前正在执行的线程为 被阻塞的线程+在 com pletion port 上等待的线程= 池子里的所有线程的个数
  4. 让每一个线程都在 completion port 上等待。
    GetQueuedCompletionStatus
  5. 开始对着那个文件 handle 发出一些 overlapped I/O 请求
    如下函数能发出IO操作:
    -ConnectNamePipe()
    -DeviceIoControl()
    -LockFileEx()
    -ReadFile()
    -TransactNamePipe()
    -WaitCommEvent()
    - WriteFile()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值