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 完成端口模型又是怎样实现的呢?首先我们创建一个完成端口 CreateIOCompletionPort,然后再创建一个或多个工作线程,并指定它们到这个完成端口上去读取数据。再将远程连接的套接字句柄关联到这个完成端口。工作线程调用 getQueuedCompletionStatus 方法在关联到这个完成端口上的所有套接字上等待 I/O 的完成,再判断完成了什么类型的 I/O,然后接着发出 WSASend 和 WSARecv,并继续下一次循环阻塞在 getQueuedCompletionStatus。
具体的说,一个完成端口大概的处理流程包括:
Port port = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, fixedThreadCount());
While(true){ getQueuedCompletionStatus(port, ioResult); }
if (handle != 0L) { createIoCompletionPort(handle, port, key, 0); }