您的位置:首页 > 其它

十、同步设备I/O与异步设备I/O(I/O完成端口)

2013-05-30 15:25 423 查看
I/O完成端口是一种有无数用途的绝佳的线程间通信机制

1. 打开和关闭设备

设备:能够与之进行通信的任何东西

 createFile可以打开很多设备

 缓存,标志

2. 使用文件设备

设置文件指针位置以及如何改变文件大小

每个文件内核对象都有自己的文件指针

如何追加内容到文件结尾,如何写入。

3. 执行同步设备I/O

readFile writeFile

flushfilebuffers

应用程序停止相应的最常见原因,就是因为要等待同步I/O操作完成而被阻塞

同步io会阻塞住同一线程(即发出io请求的线程)的任何其他操作

cancelsynchroncusio

4. 异步设备io

如何把异步io请求加入队列,把异步io请求加入队列是设计高性能,可伸缩性好的应用程序的本质所在

打开设备 deflagsandattribute FILE_FLAG_OVERLAPPED

在打开异步设备io的时候,我们必须在pOverlapped参数中传入一个已经初始化的VOERLAPPED结构

设定起始位置,非文件设备忽略

hEvent用来接收io完成通知的四种方法

设备驱动程序不必以陷入先出的方式来处理队列中的io请求

5. 接收io请求完成通知(4中方法)

触发设备内核对象,触发事件内核对象,使用可提醒io,使用io完成端口

当系统创建一个线程的时候,会同时创建一个与线程相关的队列。这个队列被称为异步过程调用(apc)队列。当发出一个io请求时,我们可以告诉驱动程序在调用线程的apc队列中添加一项。

6. I/O 完成端口

串行模型,并发模型(新创建线程)

但是并发模型创建大量的线程也有问题,使得线程切换用去大量的时间,而真正执行的时间不多。解决方案:io完成端口

另一个缺点:新建线程花销多 改进: 线程池

(1) 创建io完成端口(io完成端口是最复杂的内核对象)

io完成端口背后的理论是:并发运行的线程的数量必须有一个上限

createiocompletionport 执行两项任务,创建一个io完成端口,将一个设备与一个io完成端口关联起来(根据参数不同决定新建或者绑定,可依文中方式将其抽象成两个函数)。

创建io完成端口,没有传入安全性,这在所有用来创建内核对象的windows函数中,绝无仅有的。这是因为io完成端口的设计初衷就是只在一个进程中使用。

(2)五个数据结构:设备列表,io完成队列,等待线程队列(后入先出),已释放线程列表,已暂停线程列表。

GetQueuedCompletionStatus的任务基本上就是将吊桶线程切换到睡眠状态。知道指定的完成端口的io完成队列中出现一项或者等待超时

后进先出 节约资源 p312

(3)io完成端口如何管理线程池

讨论io端口为何如此有用

创建io完成端口时,指定最大并发线程数,一般设定为cpu数量。而线程池中线程数目为2*cpu数

如:双核cpu则创建4个线程。假如有4个完成请求,也只分配(释放)2个线程去处理。待处理完成后还是用这两个线程处理设下的2个io请求

那么为什么需要4个线程呢?这是因为一个线程在处理io请求的过程中,有可能调用某些函数,使得自己进入等待状态。也就是暂停了(系统将其加入暂停队列),由于某些线程暂停,现在并发数小于最大并发数,这时候剩余两个线程派上用场了。系统唤醒(释放)其中一个进行处理(如果有io完成请求的话)。假如这时候,那个由于种种原因被暂停的线程由于某些原因被唤醒,回到已释放线程队列中,这时候并发线程数3大于最大并发线程数2。即已释放线程列表中的线程数量大于最大允许的线程并发数量。

p314的描述与上文一致。

一旦一个线程调用了getqueuedcompletiionstatus,该线程会被指派给指定的完成端口。系统假定所有被指派的线程都是以完成端口名义来完成工作的。

(4)线程池中有多少线程

启发性算法。。

(5)模拟已完成的io请求

PostQueuedCompletionStatus用来将一个已完成的io通知追加到io完成端口的队列中。其有用程度令人难以置信。。它为我们提供了一种方式来与线程池中的所有线程进行通信。可以告诉线程退出。但是如果想告诉线程中的每个线程发生了什么事,那么就有问题。因为释放线程得到io完成通知并处理后,会进入下一次循环,再次调用getqueuedcompletiionStatus。这种后进先出的方式,使得另外一些线程永远无法得到通知。所以,为了完成任务,必须采用其它线程同步机制。

在关联完成端口的时候,会给每个设备分配一个完成键。

当发往源文件的io请求完成时,相应完成键ck_read表示读取操作完成,ck_write表示写入操作完成。。

p318最后几句话有问题

意思应该是:如果ck_read说明发给源文件的io请求(读)已经完成了,接下来应该对目标文件写入。。

那么ck_read, ck_write怎么发出的呢?可参见p310上部,当设备的一个异步io请求完成时,系统会检查设备是否与一个io完成端口相关联。如果设备与一个io完成端口相关联,那么系统会将该项已完成的io请求追加到io完成端口队列末尾。。

overlapped hevent可以指定是否加入完成队列。。

filecopy编程练习。。

class Cioreq : public overlapped 继承其成员,可以对其成员直接赋值,并加上一些操作。(好的设计。。)

线程的创建在哪?

IO完成端口:(详见:http://www.ibm.com/developerworks/cn/java/j-lo-iocp/

IOCP 实现的基本步骤

那么 IOCP 完成端口模型又是怎样实现的呢?首先我们创建一个完成端口 CreateIOCompletionPort,然后再创建一个或多个工作线程,并指定它们到这个完成端口上去读取数据。再将远程连接的套接字句柄关联到这个完成端口。工作线程调用 getQueuedCompletionStatus 方法在关联到这个完成端口上的所有套接字上等待 I/O 的完成,再判断完成了什么类型的 I/O,然后接着发出 WSASend 和 WSARecv,并继续下一次循环阻塞在 getQueuedCompletionStatus。

具体的说,一个完成端口大概的处理流程包括:

创建一个完成端口;

Port port = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, fixedThreadCount());


创建一个线程 ThreadA;
ThreadA 线程循环调用 GetQueuedCompletionStatus 方法来得到 I/O 操作结果,这个方法是一个阻塞方法;

While(true){
getQueuedCompletionStatus(port, ioResult);
}


主线程循环调用 accept 等待客户端连接上来;
主线程 accept 返回新连接建立以后,把这个新的套接字句柄用 CreateIoCompletionPort 关联到完成端口,然后发出一个异步的 Read 或者 Write 调用,因为是异步函数,Read/Write 会马上返回,实际的发送或者接收数据的操作由操作系统去做。

if (handle != 0L) {
createIoCompletionPort(handle, port, key, 0);
}


主线程继续下一次循环,阻塞在 accept 这里等待客户端连接。
操作系统完成 Read 或者 Write 的操作,把结果发到完成端口。
ThreadA 线程里的 GetQueuedCompletionStatus() 马上返回,并从完成端口取得刚完成的 Read/Write 的结果。
在 ThreadA 线程里对这些数据进行处理 ( 如果处理过程很耗时,需要新开线程处理 ),然后接着发出 Read/Write,并继续下一次循环阻塞在 GetQueuedCompletionStatus() 这里
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: