win32多线程程序设计

一、创建线程                  


与调用函数的过程类似;线程只不过用CreateThread的API将函数封装起来,并产生一个与主程序同时执行的程序来调用被封装的函数。

HANDLE hThread = CreateThread (
             LPSECURITY_ATTRIBUTES lpThreadAtt,
             DWORD dwStackSize
             LPTHREAD_START_ROUTINE lpFun,
             LPVOID lpParameter,
             DWORD dwCreationFlags,
             LPDWORD lpThreadID)
    LpFun:就是指向被封装的函数的地址。
    LpParameter: 就是指向被封装的函数的参数(没有参数填入null)


下图为简单的函数调用和调用线程的过程:



注意当用CreateThread之后,产生的新的线程不会立即启动,执行的时间也是无法预期的



二、核心对象                      


当调用CreateThread()后,会产生线程核心对象(如果已经产生了线程核心对象,那么就只将此核心对象的引用计数加1;另外,win32系统中有若干种核心对象),并以句柄handle指向它。那么,显然,在线程使用完毕后就应该用 CloseHandle()函数关闭。


三、线程结束                      

既然线程是独立的运行的程序,那么主程序如何知道线程执行完毕呢?
答案是通过GetExitCodeThread(HANDLE hThread,LPDWORD lpExitCode)函数。如果线程已结束,那么线程结束的代码被放在 lpExitCode参数中带回来,如果线程尚未结束,lpExitCode带回来的值是STILL_ACTIVE。
例:
[cpp]  view plain copy
  1. DWORD WINAPI ThreadFun(LPVOID n) //线程函数:  
  2. {  
  3.     return 10;  
  4. }  
  5.   
  6. int main()  
  7. {  
  8.     hThrd = CreateThread(null,0,ThreadFun, …);  
  9.     DWORD  exitCode=0;  
  10.     for(  ;  ; ) {  
  11.         GetExitCodeThread(hThrd,&exitCode);  
  12.         if ( exitCode == STILL_ACTIVE){  
  13.                    //线程仍然在运行  
  14.         }  
  15.         else{  
  16.                    break;  
  17.         }  
  18.     }  
  19.     //exitCode保存了函数的返回值  
  20.     printf( “线程函数的返回值 : %d /n”, exitCode);  
  21.     CloseHandle(hThrd);  
  22.     return 0;  
  23. }  


四、线程的等待技术

三种等待线程的技术:

        1)使用Sleep()函数——问题是你不可能事先知道什么事情要等待多久

        2)使用busy loop,通过不断地调用GetExitCodeThread()这个函数来判断一个线程是否还在执行——问题是使用这个方法,必须持续不断的调用GetExitCodeThread(),直到其结果不再是STILL_ACTIVE,这方法不好,很浪费cpu时间,称为忙等待(busy waits)。

[cpp]  view plain copy
  1. for ( ; ; )  
  2. {  
  3.         int rc;  
  4.        rc = GetExitCodeThread(hThrd,&exitCode);  
  5.         if (!rc && exitCode != STILL_ACTIVE)  
  6.                break;  
  7. }  

3)使用WaitForSingleObject(hHandle,dwMilliseconds);

功能:某一线程中调用这个函数,此线程会被挂起:

           1、dwMilliseconds毫秒内,如果此线程所等待的对象(hHandle所指线程)变成有信号状态(被激发,即hHandle线程结束),则该函数立即返回执行函数下面的代码;

           2、超过dwMilliseconds毫秒,hHandle所指的对象还没有变成有信号状态,照样返回。

[cpp]  view plain copy
  1. //参考书上例子  
  2. //程序的目的:只用个线程 ,完成件事  
  3. int main()  
  4. {  
  5.        HANDLE hThrds[3];  
  6.    
  7.         int slot = 0;  
  8.         forint I=1 ; I<=6 ; I++){  
  9.                if( I > 3 ){  
  10.                       //已经存在个线程了  
  11.                       //等待其中的一个线程线束后,再创建线程做剩余的事情  
  12.                       //效率不高,因为线程结束的次序与它们被产生的次序不同  
  13.                      WaitForSingleObject( hThrds[slot] , INFINITE ) ;  
  14.                      CloseHandle ( hThrds[slot] );  
  15.               }  
  16.    
  17.                //构造线程  
  18.               hThrds[slot] = CreateThread(NULL,0,ThreadFunc,NULL,0,NULL);  
  19.                if (++slot>2)  
  20.                      slot=0;  
  21.        }  
  22.    
  23.         for ( slot=0; slot<3; slot++){  
  24.                //等待剩余的线程结束  
  25.               WaitForSingleObject( hThrds[slot] , INFINITE );  
  26.               CloseHandle ( hThrds[slot] );  
  27.        }  
  28. }  

上面的这段程序有一个问题,就是效率不是很高; 理想的状况是,一旦有一个线程结束,就立刻产生一个线程补上。仔细读上面的程序,你会发现它无法实现理想的状况;因为它假设线程结束的次序会和它们被产生的次序相同。比如,当依序产生了 1,2,3线程以后,一定是按1,2,3的次序结束。 其实不然, 也许 2比1更早结束。在这时,上面的程序并不会立刻产生一个线程填补2,而是非要等1结束了,才产生线程。那么能够实现 理想的状况吗?答案是可以,请用 WaitForMultipleObjects()  函数, 这个函数的用法基本上与 WaitForSingleObject() 差不多,在这里我就不举例说明了。
总结:
     等待线程结束:WaitForSingleObject
     等待多个线程结束:WaitForMultipleObjects
     等待多个线程结束或消息到达:MsgWaitForMultipleObjects


如何让一个线程和另外一个线程合作。在同一时间段会存在多个线程,当这些线程同时存取同一数据时,就会有问题。就像在超市储物品一样,来的时候物品箱是空,转身拿物品准备储的时候,发现物品箱已被占用了。这时,物品箱就是我所说的同一数据,人指的就是线程了。

        线程之间的协调工作由同步机制来完成。同步机制相当于线程之间的红绿灯系统,负责给某个线程绿灯而给其他线程红灯进行等待。

:对同步(synchronous)和异步进行一个说明,所谓的同步:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续下去。

       Win32 API中SendMessage()就是同步行为,而PostMessage()就是异步行为。

现在,看看第一个同步机制。


一、Critical Sections(临界区域、关键区域)


主要操作有:

        InitializeCriticalSection

        EnterCriticalSection

        LeaveCriticalSection

        DeleteCriticalSection

通过一个例子说明:

[cpp]  view plain copy
  1. CRITICAL_SECTION gBoxKey ;  
  2.   
  3. DWORD WINAPI ThreadFun(LPVOID n){  
  4.        //进入关键区域(情景:关上物品箱,拨下钥匙)  
  5.        EnterCreiticalSection (&gBoxKey ); //()  
  6.   
  7.        //处理一些不可分割的操作。。。。。  
  8.        //(情景:转身拿物品,储物品,去购物。。。。)  
  9.   
  10.        //离开关键区域(情景:打开物品箱,拿出储存的物品,插上钥匙)                 
  11.        LeaveCreiticalSection (&gBoxKey); //()  
  12. }  
  13.    
  14. void main(){  
  15.        //初始化全局锁(情景:生成物品箱的钥匙 )  
  16.        InitializeCriticalSection( &gBoxKey ) ;              
  17.          
  18.        //产生两个线程(情景:准备两个人抢一个物品箱 )  
  19.        HANDLE hMan1 = CreateThread(NULL,0,ThreadFun, ……);  
  20.        HANDLE hMan2 = CreateThread(NULL,0,ThreadFun, ……);  
  21.    
  22.        CloseHandle(hMan1);  
  23.        CloseHandle(hMan2);  
  24.    
  25.        //删除全局锁(情景:删除物品箱的钥匙 )  
  26.        DeleteCriticalSection( &gBoxKey ) ;  
  27. }  
注意:1、一旦一个线程进入一个critical section,它能够重复进入该critical section,但每次进入都有对应退出;
             2、很难定义最小锁定时间,如果资源一直被锁定,你就会阻止其他线程的执行,所以千万不要在critical section中调用Sleep()或任何Wait函数。


死锁问题

在使用临界区域的时候,有可能出现两个线程互相等待对方的资源从而形成等待的轮回,这种情况称为“死锁”。下面总结一下产生死锁的原因等。

产生死锁的原因主要是
    (1) 因为系统资源不足。
    (2) 进程运行推进的顺序不合适。
    (3) 资源分配不当等。
产生死锁的四个必要条件
    (1)互斥条件:一个资源每次只能被一个进程使用。
    (2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    (3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
    (4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁
        死锁的预防是通过破坏产生条件来阻止死锁的产生,但这种方法破坏了系统的并行性和并发性。
        死锁产生的前三个条件是死锁产生的必要条件,也就是说要产生死锁必须具备的条件,而不是存在这3个条件就一定产生死锁,那么只要在逻辑上回避了第四个条件就可以避免死锁。
        避免死锁采用的是允许前三个条件存在,但通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程链,从而避免死锁。该方法支持多个进程的并行执行,为了避免死锁,系统动态的确定是否分配一个资源给请求的进程。
预防死锁:具体的做法是破坏产生死锁的四个必要条件之一。


二、Mutexes(互斥器)


        一个时间内只能有一个线程拥有mutex,就像同一时间内只能有一个线程进入同一个Critical Section一样。无论从共享资源的思路了,还是从程序代码的编制上,使用Mutexes与使用Critical Sections几乎都没有什么区别;
当然,mutex和critical section还有一些区别
       1、锁住一个未被拥有的mutex,比锁住一个未被拥有的critical section,需要花费几乎100倍的时间;
       2、mutex可以跨进程使用,critical section只能在同一进程中使用
       3、等待一个mutex时,可以指定“结束等待”的时间长度,这样就避免了进入在锁住过程中出不来的问题(这一点下面接着说明)。


提出问题:       
        作为  Mutexes 机制的提出肯定是有其原因的;我们来看这样的一个情形,当我拿走钥匙以后,因为某些因素再也不能回来了,那么这个箱子便再也不能被使用。也就是说,进入 Critical Sections 线程若中途当掉了,那么别了线程是再也进不了 Critical Sections (一个资源就这样浪费了),那些需要进入 Critical Sections 的线程会停在入口不再执行,线程永远都结束不了。

解决

        还记得上一章学过的WaitForSingleObject吗?上一章主要用它等待线程的结束,但这个函数的作用不仅限于此,在这里,我们再前进一小步,探究WaitForSingleObject这个函数的妙用。

在这里,我遇到了一个叫mutex核心对象,mutex对激发的定义是:“当没有任何线程拥有该mutex,而且有一个线程正以Wait…()等待该mutex,该mutex就会短暂地出现激发状态,使Wait…()得以返回, 那么在其它的情况,mutex处于未激发状态”。
        好了,我们又进一步的了解了WaitForSingleObject函数,那么,如何解决Critical Sections所遇到的因难呢?当拥有mutex的线程结束前没有调用ReleaseMutex(不管该线程是当了,还是忘记调用ReleaseMutex),那么其它正以WaitForSingleObject()等待此mutex的线程就会收到WAIT_ABANDONED_0。有了这个值,我就解开难题了。

举例说明

[cpp]  view plain copy
  1. HANDLE hBoxKey;  
  2.   
  3. DWORD WINAPI ThreadFun(LPVOID n){  
  4.        //进入关键区域(情景:关上物品箱,拨下钥匙)  
  5.        WaitForSingleObject ( hMutex,INFINITE ); //  
  6.            //处理一些不可分割的操作。。。。。  
  7.            //(情景:转身拿物品,储物品,去购物。。。。)  
  8.        //离开关键区域(情景:打开物品箱,拿出储存的物品,插上钥匙)                 
  9.        ReleaseMutex ( hMutex ); //  
  10. }  
  11.    
  12. void main(){  
  13.        //初始化全局锁(情景:生成物品箱的钥匙 )  
  14.        hBoxKey = CreateMutex( NULL,FALSE,NULL );           
  15.          
  16.        //产生两个线程(情景:准备两个人抢一个物品箱 )  
  17.        HANDLE hMan1 = CreateThread(NULL,0,ThreadFun, ……);  
  18.        HANDLE hMan2 = CreateThread(NULL,0,ThreadFun, ……);  
  19.    
  20.        CloseHandle(hMan1);  
  21.        CloseHandle(hMan2);  
  22.    
  23.        //删除全局锁(情景:删除物品箱的钥匙 )  
  24.        CloseHandle( hBoxKey ) ;  
  25. }  


上一笔记讲了同步机制中的临界区域(Critical Sections)、互斥器(Mutexes),下面介绍同步机制中的另外两种。


信号量(Semaphores)


举个例子

        现在有人要租车,接待他的代理人发现还有3辆车可以用,但正在给他办理手续的时候发现还有三个人也在做同样的动作。现在,就是有四个人想租三辆车。

        我们写个程序解决租车问题,方法一就是为每辆车都加一个mutex保护,问题是如果是一家大型出租车公司就需要有成百上千的mutexes了。方法二使用单一的mutex为所有的车辆服务,但一次只能有一个店员出租,这样问题就是客户会减少。

解决

      现在我们将所有的车视为相同,车交到客户手上之前,唯一需要知道的就是现在有几辆车可以用,我们用semaphore来维护这个数字,每一个锁定动作成功,semaphore的现值就会减1。

注意:与mutex不同的是,调用ReleaseSemaphore()的那个线程,并不一定就得是调用wait...()的那个线程。任何线程都可以在任何时间调用ReleaseSemaphore(),解除被任何线程锁定的semaphore。


事件(Event)


//复习

       从第三章以来,每章都讲到了一个重要的函数,就是Wait...()系列函数。

       第三章判断一个线程是否结束:WaitForSingleObject(HANDLE hthred,...);

       第四章中判断是否能够进入锁住互斥器:WatiForSingleObject(hMutex,...);

       Wait...()函数会在核心对象被激发时返回。对于hthred而言,线程结束,意味着核心对象被激发;对于hMutex而言, hMutex不再被其它任何线程使用,意味着核心对象被激发。反正对于各种核心对象而言,一定是有某种场景的出现使得核心对象被激发,除了Event这个核心对象

===============================================


        对于Event这个核心对象而言,它的激发状态完全由程序来控制,也就是说,由自己来控制Event的激发或未激发状态( 通过SetEvent() , ResetEvent() )。当线程1因调用Wait…(hEvent)而被阻塞后,一定是某个线程调用了SetEvent( hEvent )使hEvent被设为激发状态,从而使线程1被解除阻塞继续向下运行,具体的运用参见下表:
(使用CreateEvent ()函数构造Event核心对象,CreateEvent ()的第二个参数决定了产生的Event对象是Manual(手工)方式还是Auto(自动)方式;第三个参数决定了决定了产生的Event对象初始状态是激发还是未激发)  

   
函数
EVENT对象 [Manual 方式产生 ]
EVENT对象 [Auto 方式产生 ]
Wait…()
EVENT对象变为激发状态(使得因调用Wait…()而等待的线程被唤醒)之后,不会被重置为非激发状态(必须调用ResetEvent()) EVENT对象变为激发状态(使得因调用Wait…()而等待的线程被唤醒)之后,自动重置为非激发状态
SetEvent()EVENT对象设为激发状态EVENT对象设为激发状态
ResetEvent()EVENT对象设为非激发状态EVENT对象设为非激发状态
PulseEvent()EVENT对象设为激发状态,唤醒“所有”等待中的线程,然后把EVENT对象设为非激发状态
EVENT对象设为激发状态,唤醒“一个”等待中的线程,然后把 EVENT对象设为非激发状态


注意 Event核 心对象的不同的状态(初始状态是激发还是未激发)及不同的类型( Manual[ 手工 ] 方式还是 Auto[ 自动 ] ),与之对应的处理方式也就不一样,情况太多了真是不好一一举例,以后补上 J (在下面第五章中,我举了一个例子)



前面章节介绍了线程创建等过程,现在的问题是:如何在某个线程内终止另外一个正在运行的线程?


windows核心编程中提到终止运行线程的方法

       1)线程函数自己返回

       2)线程通过调用ExitThread函数“杀死”自己,该函数将终止线程的运行并导致操作系统清理该线程使用的所有操作系统资源,但是使用的C/C++资源不会被销毁(慎用);

       3)调用TerminateThread函数,不同于ExitThread总是“杀死”主调线程,TreminateThread能杀死任何线程。但是TerminateThread函数时异步的,它告诉系统你想终止线程,但在函数返回时并不能保证线程已经终止了,线程无法正确清理(慎用)。


本章中提到的做法是:使用一个手动重置的Event对象,线程检查该Event对象的状态或是等待它,举例说明。

[cpp]  view plain copy
  1. //线程退出事件  
  2. HANDLE hExitEvent = null ;  
  3.   
  4.   
  5. //一个需要长时间运行的线程  
  6. DWORD WINAPI ThreadFun ( LPVOID p )  
  7. {  
  8.     for ( int i =0 ; i < 1000000; i++ ){   //判断线程是否要被强制结束  
  9.         /* 可能在这里大家有点疑惑,如果没有调用 SetEvent()的方法, 
  10.            hExitEvent 不是总处于未激发状态吗?哪线程不就停在这里不动了? 
  11.            答案是:这里用到了 P74提到的一个Wait… ()一个用法,当time_out 
  12.            为,检查hExitEvent的状态,如果 hExitEvent处于未激发状态, 
  13.            立刻返回WAIT_TIMEOUT,如果 hExitEvent处于激发状态,则立刻 
  14.            返回WAIT_OBJECT_0。 */  
  15.   
  16.   
  17.         if ( WaitForSingleObject ( hExitEvent , 0 ) !=WAIT_TIMEOUT ){  
  18.             //做一些退出线程前的清理工作  
  19.             return (DWORD) -1 ;  
  20.               }  
  21.     //做一些极其消耗时间的处理…  
  22.     //……  
  23.     //……  
  24.     }  
  25. }  
  26.   
  27.   
  28. void main()  
  29. {  
  30.     HANDLE hThrd;  
  31.     //构造EVENT 核心对象,初始状态处于未激发状态, Manual方式  
  32.     hExitEvent = CreateEvent ( NULL,TRUE,FALSE,NULL ) ;  
  33.          
  34.     //线程启动  
  35.     hThrd = CreateThread ( null,ThreadFun, …);  
  36.          
  37.     Sleep(1000); //等待  
  38.           
  39.     SetEvent ( hExitEvent ) ;//等待了很久,实在等不下去了,发出结束线程退出事件(激发 EVENT核心对象),迫使线程结束  
  40.     //等待线程结束  
  41.     WaitForSingleObject (hThrd, …);  
  42.     CloseHandle ( hThrd );  
  43. }  

大致的一个过程就是下图所示:




         接下来就是线程优先权,调整线程优先权很简单,无非就是用SetThreadPriority()和GetThreadPriority()等函数。但如何有效,合理的调整线程的优先权却是一个复杂的问题,正如书上所说,“如果你的目标是保持简单,那就还是避免处理[优先权]这个烫山芋吧“。






我们知道当程序调用I/O设备处理一些事情时,让主程序停下来干等I/O的完成是没有效率的。。对这个问题有下面几种解决方法:
方法一:使用另一个线程进行I/O。问题是在主线程中操控多个线程,如何设置同步机制、如何处理错误情况都是非常复杂麻烦的。

方法二:使用overlapped I/O(就是所谓的异步asynchronous I/O,第四章讲到了同步机制),你可以让这些I/O操作并行处理。


===================================================================

在对overlapped I/O的具体讨论之前,先介绍Win32中执行I/O的基本函数。

1、打开资源

[cpp]  view plain copy
  1. HANDLE CreateFile(  
  2.         LPCTSTR lpFileName,     // 指向文件名的指针   
  3.         DWORD dwDesiredAccess,  // 访问模式(写 / 读)   
  4.         DWORD dwShareMode,      // 共享模式   
  5.         LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向安全属性的指针   
  6.         DWORD dwCreationDisposition,   // 如何创建   
  7.         DWORD dwFlagsAndAttributes,    // 文件属性   
  8.         HANDLE hTemplateFile           // 用于复制文件句柄   
  9. );<span style="font-size:14px">  
  10. </span>  
        其中第六个参数dwFlagsAndAttributes是关键,可以由多个数值组合一起而完成,其中最重要的一个数值就是FILE_FLAG_OVERLAPPED,这个标记设置成功,对文件的每一个操作都将是异步的。


2、读操作

[cpp]  view plain copy
  1. BOOL ReadFile(  
  2.         HANDLE hFile,    //欲读文件  
  3.         LPVOID lpBuffer, //用于保存读入数据的一个缓冲区  
  4.         DWORD nNumberOfBytesToRead,  //要读入的字节数  
  5.         LPDWORD lpNumberOfBytesRead, //指向实际读取字节数的指针  
  6.         LPOVERLAPPED lpOverlapped  
  7.         //如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数指向一个OVERLAPPED结构。  
  8.         //该结构定义了一次异步读取操作。否则,应将这个参数设为NULL  
  9. );  


3、写操作

[cpp]  view plain copy
  1. BOOL WriteFile(  
  2.     HANDLE hFile,     // 欲写文件  
  3.     LPCVOID lpBuffer, // 数据缓存区指针  
  4.     DWORD nNumberOfBytesToWrite,    // 你要写的字节数  
  5.     LPDWORD lpNumberOfBytesWritten, // 用于保存实际写入字节数的存储区域的指针  
  6.     LPOVERLAPPED lpOverlapped       // OVERLAPPED结构体指针  
  7. );  
注意在CreateFile()的第6个参数被指定为FILE_FLAG_OVERLAPPED后,你必须在上述的lpOverlapped参数中提供一个指针,指向一个OVERLAPPED结构。

4、OVERLAPPED结构

[cpp]  view plain copy
  1. typedef struct _OVERLAPPED {     
  2.     DWORD Internal; //预留给操作系统使用。它指定一个独立于系统的状态,当GetOverlappedResult函数返回时没有设置扩展错误信息ERROR_IO_PENDING时有效     
  3.     DWORD InternalHigh; // 预留给操作系统使用。它指定长度的数据转移,当GetOverlappedResult函数返回TRUE时有效     
  4.     DWORD Offset; //该文件的位置是从文件起始处的字节偏移量。调用进程设置这个成员之前调用ReadFile或WriteFile函数。当读取或写入命名管道和通信设备时这个成员被忽略设为零     
  5.     DWORD OffsetHigh; //指定文件传送的字节偏移量的高位字。当读取或写入命名管道和通信设备时这个成员被忽略设为零。     
  6.     HANDLE hEvent; //一个手动重置的event对象,当overlapped I/O完成时即被激发。    
  7. } OVERLAPPED,*LPOVERLAPPED;  
注意第三个参数和第五个参数



接着,我们讲解具体overlap I/O的使用。对于overlapped I/O的讨论,从简单的应用开始,然后再演变到最高级的应用:

  •         激发的文件handles;
  •         激发的Event对象;
  •         异步过程调用(APCs);
  •         I/O completion ports


一、激发的文件Handles                


以文件handle作为激发机制, 熟悉一下异步过程
        1)打开文件,使用FILE_FLAG_OVERLAPPED作为参数指示为异步操作;
        2)设置OVERLAPPED结构(进行设置)
        3)调用读写操作,并将OVERLAPPED结构作为读写操作的最后一个参数

下面引用书上的例子,来说明 overlapped
[cpp]  view plain copy
  1. //功能:从指定文件的1500位置读入300个字节  
  2. int main()  
  3. {  
  4.     BOOL rc;  
  5.     HANDLE hFile;  
  6.     DWORD numread;  
  7.     OVERLAPPED overlap;  
  8.     char buf[512];  
  9.     char *szPath=” x://xxxx/xxxx” ;  
  10.      
  11.     //检查系统,确定是否支持 overlapped,(NT以上操作系统支持OVERLAPPED)  
  12.     CheckOsVersion();  
  13.      
  14.     // 以overlapped 的方式打开文件  
  15.     hFile = CreateFile( szPath, // 将被打开的文件  
  16.                     GENERIC_READ,  
  17.                     FILE_SHARE_READ|FILE_SHARE_WRITE,  
  18.                     NULL,  
  19.                     OPEN_EXISTING,  
  20.                     FILE_FLAG_OVERLAPPED,  
  21.                     NULL  
  22.                 );  
  23.      // OVERLAPPED结构实始化为  
  24.     memset(&overlap, 0, sizeof(overlap));  
  25.      //指定文件位置是;  
  26.     overlap.Offset = 1500;  
  27.    
  28.     rc = ReadFile(hFile,buf,300,&numread,&overlap);  
  29.         //因为是overlapped操作, ReadFile会将读文件请求放入读队列之后立即返回( false),  
  30.         //而不会等到文件读完才返回 (true)  
  31.     if (rc){  
  32.                //文件真是被读完了,rc为 true  
  33.                // 或当数据被放入cache中,或操作系统认为它可以很快速地取得数据, rc为true  
  34.     }  
  35.     else{  
  36.         if (GetLastError() == ERROR_IO_PENDING)  
  37.         {  
  38.                       //当错误是ERROR_IO_PENDING,那意味着读文件的操作还在进行中  
  39.                       //等候,直到文件读完  
  40.             WaitForSingleObject(hFile, INFINITE);  
  41.             rc = GetOverlappedResult(hFile,&overlap,&numread,FALSE);  
  42.              //上面二条语句完成的功能与下面一条语句的功能等价: GetOverlappedResult(hFile,&overlap,&numread,TRUE);  
  43.          }  
  44.          else{  
  45.             //出错了  
  46.          }  
  47.     }  
  48.     CloseHandle(hFile);  
  49.     return EXIT_SUCCESS;  
  50. }  
        通过上面的程序,对overlapped就有一个大概的印象。异步的ReadFile操作后会立即返回执行下面的操作,不管读操作有没有完成。

二、激发的Event对象                   


            以文件handle作为激发机制,有一个明显的缺陷:系统有可能同时接受数个操作而它们都使用同一个handle,那就没办法说出到底哪一个overlapped操作完成了。
        怎么办?
        我们用event搭配overlapped,将OVERLAPPED结构中的最后一参数hEvent设为手动重置(必须是手动重置),每一个操作有自己的event对象;然后调用WaitForMultipleObjects()来等待其中之一或全部完成。

引用书上的例子
[cpp]  view plain copy
  1. //程序片段:  
  2. int main()  
  3. {  
  4.     int i;  
  5.     BOOL rc;  
  6.     char *szPath=” x://xxxx/xxxx” ;  
  7.       
  8.     // 以overlapped 的方式打开文件  
  9.     ghFile = CreateFile( szPath,  
  10.                     GENERIC_READ,  
  11.                     FILE_SHARE_READ|FILE_SHARE_WRITE,  
  12.                     NULL,  
  13.                     OPEN_EXISTING,  
  14.                     FILE_FLAG_OVERLAPPED,  
  15.                     NULL  
  16.                 );  
  17.    
  18.     for (i=0; i<MAX_REQUESTS; i++){    //对同一文件按几个部分按 overlapped方式同时读  
  19.         QueueRequest(i, i*16384, READ_SIZE);//注意看QueueRequest函数是如何运做的  
  20.     }  
  21.         // 等候所有操作结束;  
  22.         //隐含条件:当一个操作完成时,其对应的 event对象会被激活  
  23.     WaitForMultipleObjects(  
  24.         MAX_REQUESTS, ghEvents, TRUE, INFINITE);  
  25.    
  26.     // 收尾操作  
  27.     for (i=0; i<MAX_REQUESTS; i++){  
  28.         DWORD dwNumread;  
  29.         rc = GetOverlappedResult(  
  30.                                 ghFile,  
  31.                                 &gOverlapped[i],  
  32.                                 &dwNumread,  
  33.                                 FALSE);  
  34.         CloseHandle(gOverlapped[i].hEvent);  
  35.     }  
  36.    
  37.     CloseHandle(ghFile);  
  38.    
  39.     return EXIT_SUCCESS;  
  40. }  
  41.    
  42. //当读操作完成以后,gOverlapped[nIndex].hEvent会系统被激发  
  43. int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount){  
  44.     //构造一个MANUAL型的 event对象  
  45.     ghEvents[nIndex] = CreateEvent(NULL, TRUE, FALSE, NULL) ;  
  46.     //将此event 对象置入OVERLAPPED结构  
  47.     gOverlapped[nIndex].hEvent = ghEvents[nIndex];  
  48.     gOverlapped[nIndex].Offset = dwLocation;  
  49.    
  50.     for (i=0; i<MAX_TRY_COUNT; i++){ //这里用了个循环读,P162有解释不是太理解  
  51.         //文件ghFile 唯一  
  52.         rc = ReadFile(ghFile, gBuffers[nIndex],&dwNumread,&gOverlapped[nIndex]);  
  53.          
  54.         if (rc) return TRUE; //处理成功  
  55.    
  56.         err = GetLastError();  
  57.         if (err == ERROR_IO_PENDING){  
  58.             //当错误是ERROR_IO_PENDING,那意味着读文件的操作还在进行中  
  59.             return TRUE;  
  60.         }  
  61.    
  62.         // 处理一些可恢复的错误  
  63.         if ( err == ERROR_INVALID_USER_BUFFER ||  
  64.              err == ERROR_NOT_ENOUGH_QUOTA ||  
  65.              err == ERROR_NOT_ENOUGH_MEMORY ){  
  66.                      Sleep(50);  
  67.                       continue;// 重试  
  68.         }  
  69.           
  70.         // 如果GetLastError() 返回的不是以上列出的错误,放弃  
  71.         break;  
  72.     }  
  73.     return -1;  
  74. }  


三、使用异步过程调用(APCs)    


上面的程序片段使用WaitForMultipleObjects函数来等待,会有两个问题
第一, 只能等待小于64个的对象,这是WaitForMultipleObjects函数本身所带来的限制。
第二,你必须不断根据“哪一个handle被激发”而计算如何处理。那个这个程序的结构可不是很清晰,以后维护可不方便


解决:这两个问题可以靠一个异步过程调用(APC)解决,只要使用“Ex”版的ReadFlie和WriteFile就可以调用这个机制,这两个函数允许你指定一个额外的参数,是一个callback函数地址。APCs的核心思想就是当overlapped I/O操作完成时,调用这个callback函数。


例子

[cpp]  view plain copy
  1. //反馈函数,供overlapped I/O操作完成时调用  
  2. VOID WINAPI FileIOCompletionRoutine(DWORD dwErrorCode, //完成码  
  3.                    DWORD dwNumberOfBytesTransfered,   // 被传递的字节数目  
  4.                    LPOVERLAPPED lpOverlapped )        // 指向OVERLAPPED 结构的指针                   
  5. {  
  6.     //这里运用了一个技巧,因为使用 APCs技术;那么OVERLAPPED 结构的event栏位没有什么用,可以用它来传递一些参数。  
  7.     //在这里利用它传递序号,以表明是誰完成了 overlapped I/O  
  8.     int nIndex = (int )(lpOverlapped->hEvent);  
  9.       
  10.     switch ( nIndex )   //针对nIndex ,做一些操作  
  11.     {  
  12.         case 1 :   
  13.                               // 做一些操作。。。   
  14.                 break;  
  15.         case 2 :   
  16.                 // 做一些操作。。。   
  17.                 break;  
  18.         //…  
  19.     }  
  20.          
  21.     //如果所有overlapped I/O都处理完毕,将全局 event激发,使主程序结束  
  22.     if (++nCompletionCount == MAX_REQUESTS)  
  23.         SetEvent(ghEvent);  
  24. }  
  25.   
  26. int main()  
  27. {  
  28.       
  29.     ghEvent=CreateEvent(NULL,TRUE,FALSE, NULL );//构造全局event        
  30.     ghFile = CreateFile( szPath,    
  31.                     GENERIC_READ,  
  32.                     FILE_SHARE_READ|FILE_SHARE_WRITE,  
  33.                     NULL,  
  34.                     OPEN_EXISTING,  
  35.                     FILE_FLAG_OVERLAPPED,  
  36.                     NULL  
  37.      );               // 以overlapped 的方式打开文件  
  38.       
  39.      for (i=0; i<MAX_REQUESTS; i++){  
  40.         //将同一文件按几个部分按 overlapped方式同时读,注意看QueueRequest函数是如何运做的  
  41.         QueueRequest(i, i*16384, READ_SIZE);  
  42.        }  
  43.        // 等待所有操作完成  
  44.        WaitForSingleObjectEx(ghEvent, INFINITE, TRUE );  
  45.    
  46.        CloseHandle(ghFile);  
  47.        return EXIT_SUCCESS;  
  48. }  
  49.   
  50. int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount)  
  51. {  
  52.       
  53.     gOverlapped[nIndex].hEvent = (HANDLE)nIndex; //记录overlapped 的序号  
  54.     gOverlapped[nIndex].Offset = dwLocation;     //记录文件开始点  
  55.       
  56.     ReadFileEx(ghFile,gBuffers[nIndex],dwAmount,&gOverlapped[nIndex],FileIOCompletionRoutine);   //当读文件完成后自动调用FileIOCompletionRoutine  
  57. }  

           以上的三个程序片段都是对 overlapped I/O 具体运用;对付一般的I/O操作应是可以了,如果你需要更高的效率,那么你就需要书上提到的 I/O Completion Ports 了。
 ===========================================

下面完全转载:

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,  //不使用任何port
0,       //这个参数用于在线程间传递参数,此时设为空
            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);



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值