完成端口详细解析.doc
《完成端口详细解析.doc》由会员分享,可在线阅读,更多相关《完成端口详细解析.doc(18页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、关于完成端口(IOCP)的文章汇总 - C/C+版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明http:/ Completion Ports试图解决什么样的问题。 写一个IO Intensive服务器程序,对每一个客户请求生成一个新的child process/worker thread来处理,每个process/thread使用同步IO,这是最经典古老的解法了。在这之上的改进是prefork 多个process 或者使用线程池。(使用process或thread,原理都差不多,thread的context switch花销要比process switch要小。为了论述简单,
2、下面只讨论线程。) 这种结构的并发性并不高,哪怕你用C+, C甚至汇编来写,效率都不会很高,究其原因,在于两点: 一同步IO,每个线程大多数时间在等IO request的结束。IO相对于CPU,那是极极慢的。我翻了翻手里的Computer Architecture, A Quantitative Approach第二版,1996年出的,里面对CPU Register, CPU Cache, RAM, Disk,列的access time如下: Java代码 1. Registers: 2-5 nano seconds 2. CPU Cache: 3-10 nano seconds 3. RAM
3、: 80-400 nano seconds 4. Disk: 5 000 000 nano seconds (5 milli seconds) 如今CPU又按照摩尔定律发展了十年后,这个硬盘还是机械式的磁头移来移去读写,尽管如今disk controller都有cache,也在发展,但和CPU相比,差距越来越大。(谁有最新数据可以贴上来。) 二生成数量大大超过CPU总数的线程。这样做有两个弊端,第一是每个线程要占用内存,Windows底下每个thread自己stack的省缺大小为1M,32位程序下一个用户程序最大能利用的内存也就3G,生成3000个线程,内存就没了。当然有人说64位下面,可以随
4、便浪费,那么,第二个弊端,就无法避免了 生成大量的线程,CPU必然会花费大量的cpu cycles在线程之间进行切换。如今市场上价格适中的服务器也就2 cpu x 4 core = 8 核而已。生成那么多的线程,CPU在切换线程上花的功夫可能比干正经事还要多。 明白了原因,就可以寻找改进方法。首先,使用异步IO。现在所有主流OS,都提供异步IO(non-blocking IO),连Java这种跨平台的编程环境都在版本1.4里开始支持异步IO了。但是,光有异步IO,这是不够的。论坛里有人发贴子问过,“我的线程发个IO Request,异步IO,直接返回了,然后我的线程干什么?” 异步IO是操作系
5、统提供的机制,我们还需要设计我们程序的结构,使异步IO和线程结合起来,可以充分利用异步IO带来的好处,同时必须控制同时运行线程的数量,减少thread context switch的开销。 IO Completion Port, 是微软针对上述思想,在Windows内核级别,提供的解决方案。 从抽象高度去理解IO Completion Port,可以把它想成一个magic port,一边有一个队列是IO驱动程序处理好的IO数据,另一边是一个小小的线程池,这个port把io数据交给线程池里的线程来处理。同时,别的线程启动了IO异步请求后通知这个port一声,“嘿,注意了,一会儿这个IO hand
6、le 会有个数据包传过来要处理。” 这个port回答,“好,我注意一下这个handle。”。 下面我们具体看一下Io Completion Port这个内核对象以及使用。 要创建IoCompletionPort,呼叫Win32函数CreateIoCompletionPort。这个函数一身两用,创建IoCompletionPort也是它,往建好的IoCompletionPort里面加device handle也是它。 Java代码 1. HANDLE CreateIoCompletionPort( 2. HANDLE hfile, 3. HANDLE hExistingCompPort, 4.
7、ULONG_PTR CompKey, 5. DWORD dwNumberOfConcurrentThreads); 6. 7. / 创建IoCompletionPort 8. 9. HANDLE hCp; 10. hCp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 4); 创建IoCompletionPort头三个参数都是NULL之类的,只有第四个参数,用来配置这个生成的IoCompletionPort所允许同时运行的最大线程数目。 创建好的IoCompletionPort kernel object,拥有两个队列。一个是De
8、vice List,包含所有通过这个IoCompletionPort管理的异步IO请求 的Device Handle。另外一个是I/O Completion Queue (FIFO),Device Handle对应的IO驱动程序处理好的IO数据,放在这个队列里。 为了能在Device List这个队列里面加个entry,用户程序将再一次使用CreateIoCompletionPort 这个函数。 Java代码 1. CreateIoCompletionPort(myHandle, hCp, myKey, 0); 第一个参数是个IO Handle(Windows不限制handle类型,File,
9、 Directory, Serial Port, Parallel port, Mailslot server, Mailslot client, pipe, socket等等都可以),第二个参数是以前创建的IoCompletionPort handle. 这个IO Handle将放到IoCompletionPort handle的Device List那个队列里去。第三个参数是个long整数,是用来identify程序Request Context的。因为现在程序不是一个线程来处理客户Request了,而是不同的线程来处理。在一个线程里按顺序一二三四五来实现程序逻辑的方式是不行了,因此作为程
10、序员你要把逻辑的Context记下来,让不同的线程得到这个Context,根据当前的状态,来执行相关的代码。这个completion key,是找到相映的context的key, index, hash code,pointer, whatever. 第二个队列,IO Completion Queue,是由OS往里面插入entry的。OS在处理好了IO异步请求之后,察看一下这个Device handle是否是放在某个Completion Port里面,如果是,OS就在Completion Port的Completion Queue里面加个Entry。这个Entry包括下列数据。 Java代码
11、1. 1Number of bytes transferred 2. 2Completion key 3. 3Pointer to I/O requests OVERLAPPED structure 4. 4Error code 下面来看看IO Completion Port是怎么管理线程的。 前面说Completion Port有个线程池,这种说法并不是很贴切。Completion Port本身并不创建线程,而只是掌管三个thread队列: Java代码 1. 1Inactive threads waits IO Completion Port 2. 2Active running thre
12、ads 3. 3Threads paused by other reasons, like waiting for something else (i.e. calls WaitForSingleObject, or even stupid but valid, calls Sleep). 线程由程序创建,然后加入第一个队列(waits on IO Completion Port)。为了加入这个队列线程要呼叫一个函数,GetQueuedCompletionStatus。 Java代码 1. BOOL GetQueuedCompletionStatus( 2. HANDLE hCompPort,
13、 3. PDWORD pdwNumBytes, 4. PULONG_PTR CompKey, 5. OVERLAPPED* ppOverlapped, 6. DWORD dwMilliseconds); 第一个参数是handle to Completion Port,线程通知OS本线程要加入这个Completion Port的第一个队列。这个函数会block当前线程,使其处于inactive状态。 现在再去看看图二的I/O Completion Queue,OS在一份IO异步请求处理好了后,会在这里插入个entry,Completion Port在收到entry后,看看线程池里面有没有空闲没事
14、做的线程,如果有,不要忘记我们创建这个Completion Port时候规定了个最大同时运行线程数量,如果当前运行线程数量小于这个最大值,那么就把这个线程放到第二个(active running)的队列上去,让这个线程运行起来。前面不是说线程在GetQueuedCompletionStatus上面block了么,现在这个函数返回了,继续运行程序的代码。通过这个最大同时运行线程数量,保证了不会有太多的线程在运行,Viola! 本文开头分析的几个问题全解决了。即是异步IO,又把异步IO和线程池结合了起来,还控制了当前运行线程数量。Its BEAUTIFUL! 这个线程处理完程序逻辑后,呼叫一下Ge
15、tQueuedCompletionStatus,又回到了第一个队列。有意思的是这个队列的逻辑是Last In First Out。如果又有IO数据等待线程处理,这个线程可以继续执行,不用进行Context Switch,典型的能者多劳啊,越能干的人干的越多。 这个线程在处理程序逻辑的过程中,可能会因为别的原因而变成inactive,比如在等别的资源(WaitForSingleObject),或者变态一点,自己来了个Sleep,这时线程就给放到第三个队列去了。 这里有个有趣的现象,假如开始我们在第一个队列里面放三个线程,而最大同时运行线程数量设为2,在两个线程跑起来之后,第三个就不跑了,如果这时
16、运行中的某个线程因为等别的资源而变为inactive,那么第三个线程也开始跑起来,同时运行线程数量还是2,这时那个等别的资源的线程等到资源了,又开始跑了起来,这时同时运行线程数量就是3,比设定的2要大。 推荐的最大同时运行线程数量一般为CPU的总数,但是如果运行的线程还要等别的资源,建议把这个数目稍微设大一点,这样并发率会更高. 关于这一点微软的描述如下:An I/O completion port is associated with the process that created it and is not shareable between processes. However, a
17、single handle is shareable between threads in the same process. For another article about I/O completion ports, see Inside I/O Completion Ports in the Microsoft TechNet Library at http:/ and ConcurrencyThe most important property of an I/O completion port to consider carefully is the concurrency val
18、ue. The concurrency value of a completion port is specified when it is created with CreateIoCompletionPort via theNumberOfConcurrentThreads parameter. This value limits the number of runnable threads associated with the completion port. When the total number of runnable threads associated with the c
19、ompletion port reaches the concurrency value, the system blocks the execution of any subsequent threads associated with that completion port until the number of runnable threads drops below the concurrency value.The most efficient scenario occurs when there are completion packets waiting in the queu
20、e, but no waits can be satisfied because the port has reached its concurrency limit. Consider what happens with a concurrency value of one and multiple threads waiting in the GetQueuedCompletionStatus function call. In this case, if the queue always has completion packets waiting, when the running t
21、hread calls GetQueuedCompletionStatus, it will not block execution because, as mentioned earlier, the thread queue is LIFO. Instead, this thread will immediately pick up the next queued completion packet. No thread context switches will occur, because the running thread is continually picking up com
22、pletion packets and the other threads are unable to run.Note In the previous example, the extra threads appear to be useless and never run, but that assumes that the running thread never gets put in a wait state by some other mechanism, terminates, or otherwise closes its associated I/O completion p
23、ort. Consider all such thread execution ramifications when designing the application.The best overall maximum value to pick for the concurrency value is the number of CPUs on the computer. If your transaction required a lengthy computation, a larger concurrency value will allow more threads to run.
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 完成 端口 详细 解析
限制150内