win32多线程程序设计笔记(第六章下)
接着,我们讲解具体overlap I/O的使用。对于overlapped I/O的讨论,从简单的应用开始,然后再演变到最高级的应用:
一、激发的文件Handles
//反馈函数,供overlapped I/O操作完成时调用VOID WINAPI FileIOCompletionRoutine(DWORD dwErrorCode, //完成码 DWORD dwNumberOfBytesTransfered, // 被传递的字节数目 LPOVERLAPPED lpOverlapped ) // 指向OVERLAPPED 结构的指针 { //这里运用了一个技巧,因为使用 APCs技术;那么OVERLAPPED 结构的event栏位没有什么用,可以用它来传递一些参数。 //在这里利用它传递序号,以表明是誰完成了 overlapped I/O int nIndex = (int )(lpOverlapped->hEvent); switch ( nIndex ) //针对nIndex ,做一些操作 { case 1 : // 做一些操作。。。 break; case 2 : // 做一些操作。。。 break; //… } //如果所有overlapped I/O都处理完毕,将全局 event激发,使主程序结束 if (++nCompletionCount == MAX_REQUESTS) SetEvent(ghEvent);}int main(){ ghEvent=CreateEvent(NULL,TRUE,FALSE, NULL );//构造全局event ghFile = CreateFile( szPath, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL ); // 以overlapped 的方式打开文件 for (i=0; i<MAX_REQUESTS; i++){ //将同一文件按几个部分按 overlapped方式同时读,注意看QueueRequest函数是如何运做的 QueueRequest(i, i*16384, READ_SIZE); } // 等待所有操作完成 WaitForSingleObjectEx(ghEvent, INFINITE, TRUE ); CloseHandle(ghFile); return EXIT_SUCCESS;}int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount){ gOverlapped[nIndex].hEvent = (HANDLE)nIndex; //记录overlapped 的序号 gOverlapped[nIndex].Offset = dwLocation; //记录文件开始点 ReadFileEx(ghFile,gBuffers[nIndex],dwAmount,&gOverlapped[nIndex],FileIOCompletionRoutine); //当读文件完成后自动调用FileIOCompletionRoutine}下面完全转载:
I/O Completion Ports也许是最好的方法了(看看书P172-P173你是否会有这种感觉呢?);但I/O Completion Ports 好像很难理解,我试着从自己理解的角度来写写心得(也许不对);描述:把I/O Completion Ports看作容器,那么在这个容器中放置若干个线程(书上说最好是cpu个数*2+2),这些个线程随时随地的将被激活去服务I/O请求。 为什么I/O Completion Ports会很有效率?我是这样想的:首先容器中保持的线程可以随时承担服务I/O请求的任务,其主要特点是:两个线程在不同的时间可以服务同一个I/O请求;第二:这些个线程的调度由系统选择安排。系统总是在它认为最合适的时机去调度线程做最合适的事。书上还说了其它的一些有利于效率的工作,我就不再一一描述了。 下面我们来看一下运用I/O Completion Ports这是咱们最关心的: 在P179有一个操作概观:对着它,我来解释书上I/O Completion Ports 的例子。 //注意:一定要对着书,把下面用到的WIN32函数的每个参数的含义弄清楚,这很重要//――――――产生一个I/O completion port ① //构造一个I/O completion port ghCompletionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE,NULL, //不使用任何port0, //这个参数用于在线程间传递参数,此时设为空 0 // 使用默认的线程数); //――――――让它和一个文件handle产生关联 ② //将socket关联到①产生的I/O completion port,那么以后发生在这个socket//上的任何I/O操作,都由此I/O completion port中的线程处理CreateIoCompletionPort( (HANDLE)newsocket, //注意,这里是socket;原因见书P184 ghCompletionPort, //指定的I/O completion port (DWORD)pKey, //一个指针,指向自定义的结构 0 // 使用默认的线程数 ); /* pKey,用于记录在newsocket上发生的操作,在这里指向一个自定义的结构,此结构中保存从客户端读入的数据和写回客户的数据*///――――――产生一堆线程 ③ CreateWorkerThreads(); //――――――下面是④、⑤步 //下面这段代码在线程函数ThreadFunc中for (;;){//――――――让每一个线程都在completion port上等待 ④ //下面这个函数相当于WaitForSingleObject;[提醒一下:这种等待不是忙等待] //当一个线程读操作完成后,此函数返回 bResult = GetQueuedCompletionStatus( ghCompletionPort, //指定在哪一个端口上等待 &dwNumRead, &(DWORD) pCntx, //一个指针,收到CreateIoCompletionPort所定义的key &lpOverlapped, INFINITE );/*说明:pCntx就是CreateIoCompletionPort()中参数pkey;读出的数据被指定放入pkey->InBuffer中 ,仔细看看程序代码及p183的叙述,你就会发现在I/O Completion Ports中的线程是通过pkey来判别不同的客户端的*/ if (bResult==读完 ) { //将读出的数据保存到pCntx->OutBuffer中//注意:pCntx->nOutBufIndex记录着当前客户端已读出的数据的位置 char *pch = &pCntx->OutBuffer[pCntx->nOutBufIndex++]; *pch++ = pCntx->InBuffer[0];*pch = '/0'; if (pCntx->InBuffer[0] == '/n'){//如果读完了,将收到的信息写回客户端//――――――开始对着那个文件handle发出一些overlapped I/O请求 ⑤ WriteFile( (HANDLE)(pCntx->sock), pCntx->OutBuffer, pCntx->nOutBufIndex, &pCntx->dwWritten, &pCntx->ovOut ); pCntx->nOutBufIndex = 0;}//没有读完//――――――开始对着那个文件handle发出一些overlapped I/O请求 ⑤/*下面这一段我将书上的IssueRead(pCntx)拿掉了,替换它的是IssueRead(pCntx)函数的实现*///读操作,引发I/O completion port的操作 bResult = ReadFile( (HANDLE)pCntx->sock, //记取客户端的socket pCntx->InBuffer, //将从客户端的中读取的字符写入InBuffer 1, //每次读取一个字符 &numRead, &pCntx->ovIn //嘿!这家伙在这没啥用 ); } 上面程序片段没有列出“避免Completion Packets”的程序代码。当写入操作完成时,I/O Completion port将收到一个Packets以说明写入操作成功与否;如果写入操作的结果(成功或失败)不是很重要,那么我们肯定不希望在每次写入操作后都接收这样一个Packets(因为浪费时间),我要屏蔽它,可以向下面这样: OVERLAPPED overlap; //对hEvent向下面这样处理 overlap.hEvent = CreateEvent(…); overlap.hEvent = (HANDLE)((DWORD)overlap.hEvent | 0x1); //那么 “写操作”使用这个经过处理的 overlap后,I/O Completion port不会再发Packets WriteFile(….&overlap);