如题

解决方案 »

  1.   

    The WaitForMultipleObjects function returns when one of the following occurs:?Either any one or all of the specified objects are in the signaled state.
    ?The time-out interval elapses. DWORD WaitForMultipleObjects(    DWORD nCount, // number of handles in the object handle array 
        CONST HANDLE *lpHandles, // pointer to the object-handle array 
        BOOL bWaitAll, // wait flag 
        DWORD dwMilliseconds  // time-out interval in milliseconds 
       );
     ParametersnCountSpecifies the number of object handles in the array pointed to by lpHandles. The maximum number of object handles is MAXIMUM_WAIT_OBJECTS. lpHandlesPoints to an array of object handles. For a list of the object types whose handles can be specified, see the following Res section. The array can contain handles of objects of different types. 
    Windows NT: The handles must have SYNCHRONIZE access. For more information, see Access Masks and Access Rights. bWaitAllSpecifies the wait type. If TRUE, the function returns when the state all objects in the lpHandles array is signaled. If FALSE, the function returns when the state of any one of the objects set to is signaled. In the latter case, the return value indicates the object whose state caused the function to return. dwMillisecondsSpecifies the time-out interval, in milliseconds. The function returns if the interval elapses, even if the conditions specified by the bWaitAll parameter are not met. If dwMilliseconds is zero, the function tests the states of the specified objects and returns immediately. If dwMilliseconds is INFINITE, the function's time-out interval never elapses.  Return ValuesIf the function succeeds, the return value indicates the event that caused the function to return.
    If the function fails, the return value is WAIT_FAILED. To get extended error information, call GetLastError. 
    The return value upon success is one of the following values: Value Meaning
    WAIT_OBJECT_0 to (WAIT_OBJECT_0 + nCount - 1) If bWaitAll is TRUE, the return value indicates that the state of all specified objects is signaled. If bWaitAll is FALSE, the return value minus WAIT_OBJECT_0 indicates the lpHandles array index of the object that satisfied the wait. If more than one object became signalled during the call, this is the array index of the signalled object with the smallest index value of all the signalled objects.
    WAIT_ABANDONED_0 to (WAIT_ABANDONED_0 + nCount - 1) If bWaitAll is TRUE, the return value indicates that the state of all specified objects is signaled and at least one of the objects is an abandoned mutex object. If bWaitAll is FALSE, the return value minus WAIT_ABANDONED_0 indicates the lpHandles array index of an abandoned mutex object that satisfied the wait.
    WAIT_TIMEOUT The time-out interval elapsed and the conditions specified by the bWaitAll parameter are not satisfied.
     ResThe WaitForMultipleObjects function determines whether the wait criteria have been met. If the criteria have not been met, the calling thread enters an efficient wait state, consuming very little processor time while waiting for the criteria to be met.
    When bWaitAll is TRUE, the function's wait operation is completed only when the states of all objects have been set to signaled. The function does not modify the states of the specified objects until the states of all objects have been set to signaled. For example, a mutex can be signaled, but the thread does not get ownership until the states of the other objects are also set to signaled. In the meantime, some other thread may get ownership of the mutex, thereby setting its state to nonsignaled.Before returning, a wait function modifies the state of some types of synchronization objects. Modification occurs only for the object or objects whose signaled state caused the function to return. For example, the count of a semaphore object is decreased by one.
    The WaitForMultipleObjects function can specify handles of any of the following object types in the lpHandles array: Object Description
    Change notification The FindFirstChangeNotification function returns the handle. A change notification object's state is signaled when a specified type of change occurs within a specified directory or directory tree.
    Console input The handle is returned by the CreateFile function when the CONIN$ value is specified, or by the GetStdHandle function. The object's state is signaled when there is unread input in the console's input buffer, and it is nonsignaled when the input buffer is empty.
    Event The CreateEvent or OpenEvent function returns the handle. An event object's state is set explicitly to signaled by the SetEvent or PulseEvent function. A manual-reset event object's state must be reset explicitly to nonsignaled by the ResetEvent function. For an auto-reset event object, the wait function resets the object's state to nonsignaled before returning. Event objects are also used in overlapped operations, in which the state is set by the system.
    Mutex The CreateMutex or OpenMutex function returns the handle. A mutex object's state is signaled when it is not owned by any thread. The wait function requests ownership of the mutex for the calling thread, changing the mutex's state to nonsignaled when ownership is granted.
    Process The CreateProcess or OpenProcess function returns the handle. A process object's state is signaled when the process terminates.
    Semaphore The CreateSemaphore or OpenSemaphore function returns the handle. A semaphore object maintains a count between zero and some maximum value. Its state is signaled when its count is greater than zero and nonsignaled when its count is zero. If the current state is signaled, the wait function decreases the count by one.
    Thread The CreateProcess, CreateThread, or CreateRemoteThread function returns the handle. A thread object's state is signaled when the thread terminates.
    Timer The CreateWaitableTimer or OpenWaitableTimer function returns the handle. Activate the timer by calling the SetWaitableTimer function. The state of an active timer is signaled when it reaches its due time. You can deactivate the timer by calling the CancelWaitableTimer function. The state of an active timer is signaled when it reaches its due time. You can deactivate the timer by calling the CancelWaitableTimer function.
     In some circumstances, you can specify a handle of a file, named pipe, or communications device as a synchronization object in lpHandles. However, their use for this purpose is discouraged.
    You have to be careful when using the wait functions and DDE. If a thread creates any windows, it must process messages. DDE sends messages to all windows in the system. If you have a thread that uses a wait function with no time-out interval, the system will deadlock. Therefore, if you have a thread that creates windows, use MsgWaitForMultipleObjects or MsgWaitForMultipleObjectsEx, rather than WaitForMultipleObjects.
      

  2.   

    转贴:来自 闻怡洋 http://www.vchelp.net/
    由于进程/线程间的操作是并行进行的,所以就产生了一个数据的问题同步,我们先看一段代码: int iCounter=0;//全局变量
    DOWRD threadA(void* pD)
    {
    for(int i=0;i<100;i++)
    {
    int iCopy=iCounter;
    //Sleep(1000);
    iCopy++;
    //Sleep(1000);
    iCounter=iCopy;
    }
    }现在假设有两个线程threadA1和threadA2在同时运行那么运行结束后iCounter的值会是多少,是200吗?不是的,如果我们将Sleep(1000)前的注释去掉后我们会很容易明白这个问题,因为在iCounter的值被正确修改前它可能已经被其他的线程修改了。这个例子是一个将机器代码操作放大的例子,因为在CPU内部也会经历数据读/写的过程,而在线程执行的过程中线程可能被中断而让其他线程执行。变量iCounter在被第一个线程修改后,写回内存前如果它又被第二个线程读取,然后才被第一个线程写回,那么第二个线程读取的其实是错误的数据,这种情况就称为脏读(dirty read)。这个例子同样可以推广到对文件,资源的使用上。 
    那么要如何才能避免这一问题呢,假设我们在使用iCounter前向其他线程询问一下:有谁在用吗?如果没被使用则可以立即对该变量进行操作,否则等其他线程使用完后再使用,而且在自己得到该变量的控制权后其他线程将不能使用这一变量,直到自己也使用完并释放为止。经过修改的伪代码如下: int iCounter=0;//全局变量
    DOWRD threadA(void* pD)
    {
    for(int i=0;i<100;i++)
    {
    ask to lock iCounter
    wait other thread release the lock
    lock successful

    {
    int iCopy=iCounter;
    //Sleep(1000);
    iCopy++;
    }
    iCounter=iCopy;

    release lock of iCounter
    }
    }幸运的是OS提供了多种同步对象供我们使用,并且可以替我们管理同步对象的加锁和解锁。我们需要做的就是对每个需要同步使用的资源产生一个同步对象,在使用该资源前申请加锁,在使用完成后解锁。接下来我们介绍一些同步对象: 临界区:临界区是一种最简单的同步对象,它只可以在同一进程内部使用。它的作用是保证只有一个线程可以申请到该对象,例如上面的例子我们就可以使用临界区来进行同步处理。几个相关的API函数为: VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection );产生临界区 
    VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection );删除临界区 
    VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );进入临界区,相当于申请加锁,如果该临界区正被其他线程使用则该函数会等待到其他线程释放 
    BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );进入临界区,相当于申请加锁,和EnterCriticalSection不同如果该临界区正被其他线程使用则该函数会立即返回FALSE,而不会等待 
    VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection );退出临界区,相当于申请解锁 
    下面的示范代码演示了如何使用临界区来进行数据同步处理: //全局变量
    int iCounter=0;
    CRITICAL_SECTION criCounter;DWORD threadA(void* pD)
    {
    int iID=(int)pD;
    for(int i=0;i<8;i++)
    {
    EnterCriticalSection(&criCounter);
    int iCopy=iCounter;
    Sleep(100);
    iCounter=iCopy+1;
    printf("thread %d : %d\n",iID,iCounter);
    LeaveCriticalSection(&criCounter);
    }
    return 0;
    }
    //in main function
    {
    //创建临界区
    InitializeCriticalSection(&criCounter);
    //创建线程
    HANDLE hThread[3];
    CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
    CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
    CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
    hThread[0]=pT1->m_hThread;
    hThread[1]=pT2->m_hThread;
    hThread[2]=pT3->m_hThread;
    //等待线程结束
    //至于WaitForMultipleObjects的用法后面会讲到。
    WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
    //删除临界区
    DeleteCriticalSection(&criCounter);
    printf("\nover\n");}接下来要讲互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。下面介绍可以用在互斥量上的API函数: 创建互斥量:
    HANDLE CreateMutex(
      LPSECURITY_ATTRIBUTES lpMutexAttributes,// 安全信息
      BOOL bInitialOwner,  // 最初状态,
      //如果设置为真,则表示创建它的线程直接拥有了该互斥量,而不需要再申请
      LPCTSTR lpName       // 名字,可以为NULL,但这样一来就不能被其他线程/进程打开
    );
    打开一个存在的互斥量:
    HANDLE OpenMutex(
      DWORD dwDesiredAccess,  // 存取方式
      BOOL bInheritHandle,    // 是否可以被继承
      LPCTSTR lpName          // 名字
    );
    释放互斥量的使用权,但要求调用该函数的线程拥有该互斥量的使用权:
    BOOL ReleaseMutex(//作用如同LeaveCriticalSection
      HANDLE hMutex   // 句柄
    );
    关闭互斥量:
    BOOL CloseHandle(
      HANDLE hObject   // 句柄
    );你会说为什么没有名称如同EnterMutex,功能如同EnterCriticalSection一样的函数来获得互斥量的使用权呢?的确没有!获取互斥量的使用权需要使用函数: DWORD WaitForSingleObject(
      HANDLE hHandle,        // 等待的对象的句柄
      DWORD dwMilliseconds   // 等待的时间,以ms为单位,如果为INFINITE表示无限期的等待
    );
    返回:
    WAIT_ABANDONED 在等待的对象为互斥量时表明因为互斥量被关闭而变为有信号状态
    WAIT_OBJECT_0 得到使用权
    WAIT_TIMEOUT 超过(dwMilliseconds)规定时间在线程调用WaitForSingleObject后,如果一直无法得到控制权线程讲被挂起,直到超过时间或是获得控制权。 讲到这里我们必须更深入的讲一下WaitForSingleObject函数中的对象(Object)的含义,这里的对象是一个具有信号状态的对象,对象有两种状态:有信号/无信号。而等待的含义就在于等待对象变为有信号的状态,对于互斥量来讲如果正在被使用则为无信号状态,被释放后变为有信号状态。当等待成功后WaitForSingleObject函数会将互斥量置为无信号状态,这样其他的线程就不能获得使用权而需要继续等待。WaitForSingleObject函数还进行排队功能,保证先提出等待请求的线程先获得对象的使用权,下面的代码演示了如何使用互斥量来进行同步,代码的功能还是进行全局变量递增,通过输出结果可以看出,先提出请求的线程先获得了控制权: int iCounter=0;DWORD threadA(void* pD)
    {
    int iID=(int)pD;
    //在内部重新打开
    HANDLE hCounterIn=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"sam sp 44"); for(int i=0;i<8;i++)
    {
    printf("%d wait for object\n",iID);
    WaitForSingleObject(hCounterIn,INFINITE);
    int iCopy=iCounter;
    Sleep(100);
    iCounter=iCopy+1;
    printf("\t\tthread %d : %d\n",iID,iCounter);
    ReleaseMutex(hCounterIn);
    }
    CloseHandle(hCounterIn);
    return 0;
    }//in main function
    {
    //创建互斥量
    HANDLE hCounter=NULL;
    if( (hCounter=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
    {
    //如果没有其他进程创建这个互斥量,则重新创建
    hCounter = CreateMutex(NULL,FALSE,"sam sp 44");
    } //创建线程
    HANDLE hThread[3];
    CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
    CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
    CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
    hThread[0]=pT1->m_hThread;
    hThread[1]=pT2->m_hThread;
    hThread[2]=pT3->m_hThread;
    //等待线程结束
    WaitForMultipleObjects(3,hThread,TRUE,INFINITE); //关闭句柄
    CloseHandle(hCounter);
    }
    }在这里我没有使用全局变量来保存互斥量句柄,这并不是因为不能这样做,而是为演示如何在其他的代码段中通过名字来打开已经创建的互斥量。其实这个例子在逻辑上是有一点错误的,因为iCounter这个变量没有跨进程使用,所以没有必要使用互斥量,只需要使用临界区就可以了。假设有一组进程在同时使用一个文件那么我们可以使用互斥量来保证该文件只同时被一个进程使用(如果只是利用OS的文件存取控制功能则需要添加更多的错误处理代码),此外在调度程序中也可以使用互斥量来对资源的使用进行同步化。
      

  3.   

    现在我们回过头来讲WaitForSingleObject这个函数,从前面的例子中我们看到WaitForSingleObject这个函数将等待一个对象变为有信号状态,那么具有信号状态的对象有哪些呢?下面是一部分: Mutex 
    Event 
    Semaphore 
    Job 
    Process 
    Thread 
    Waitable timer 
    Console input 
    互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以我们可以使用WaitForSingleObject来等待进程和线程退出。(至于信号灯,事件的用法我们接下来会讲)我们在前面的例子中使用了WaitForMultipleObjects函数,这个函数的作用与WaitForSingleObject类似但从名字上我们可以看出,WaitForMultipleObjects将用于等待多个对象变为有信号状态,函数原型如下: DWORD WaitForMultipleObjects(
      DWORD nCount,             // 等待的对象数量
      CONST HANDLE *lpHandles,  // 对象句柄数组指针
      BOOL fWaitAll,            // 等待方式,
      //为TRUE表示等待全部对象都变为有信号状态才返回,为FALSE表示任何一个对象变为有信号状态则返回
      DWORD dwMilliseconds      // 超时设置,以ms为单位,如果为INFINITE表示无限期的等待
    );返回值意义:
    WAIT_OBJECT_0 到 (WAIT_OBJECT_0 + nCount – 1):当fWaitAll为TRUE时表示所有对象变为有信号状态,当fWaitAll为FALSE时使用返回值减去WAIT_OBJECT_0得到变为有信号状态的对象在数组中的下标。
    WAIT_ABANDONED_0 到 (WAIT_ABANDONED_0 + nCount – 1):当fWaitAll为TRUE时表示所有对象变为有信号状态,当fWaitAll为FALSE时表示对象中有一个对象为互斥量,该互斥量因为被关闭而成为有信号状态,使用返回值减去WAIT_OBJECT_0得到变为有信号状态的对象在数组中的下标。
    WAIT_TIMEOUT:表示超过规定时间。 前面的例子中的如下代码表示等待三个线程都变为有信号状态,也就是说三个线程都结束。
    HANDLE hThread[3];
    CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
    CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
    CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
    hThread[0]=pT1->m_hThread;
    hThread[1]=pT2->m_hThread;
    hThread[2]=pT3->m_hThread;
    //等待线程结束
    WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
    此外,在启动和等待进程结束一文中就利用这个功能等待进程结束。通过互斥量我们可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,你的老板会要求你根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。对信号灯的操作伪代码大致如下: Semaphore sem=3;dword threadA(void*)
    {
    while(sem <= 0)
    {// 相当于 WaitForSingleObject
    wait ...
    }
    // sem > 0
    // lock the Semaphore
    sem -- ;
    do functions ...
    // release Semaphore
    sem ++ ;
    return 0;
    }这里信号灯有一个初始值,表示有多少进程/线程可以进入,当信号灯的值大于0时为有信号状态,小于等于0时为无信号状态,所以可以利用WaitForSingleObject进行等待,当WaitForSingleObject等待成功后信号灯的值会被减少1,直到释放时信号灯会被增加1。用于信号灯操作的API函数有下面这些: 创建信号灯:
    HANDLE CreateSemaphore(
      LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,// 安全属性,NULL表示使用默认的安全描述
      LONG lInitialCount,  // 初始值
      LONG lMaximumCount,  // 最大值
      LPCTSTR lpName       // 名字
    );
    打开信号灯:
    HANDLE OpenSemaphore(
      DWORD dwDesiredAccess,  // 存取方式
      BOOL bInheritHandle,    // 是否能被继承
      LPCTSTR lpName          // 名字
    );
    释放信号灯:
    BOOL ReleaseSemaphore(
      HANDLE hSemaphore,   // 句柄
      LONG lReleaseCount,  // 释放数,让信号灯值增加数
      LPLONG lpPreviousCount   // 用来得到释放前信号灯的值,可以为NULL
    );
    关闭信号灯:
    BOOL CloseHandle(
      HANDLE hObject   // 句柄
    );可以看出来信号灯的使用方式和互斥量的使用方式非常相似,下面的代码使用初始值为2的信号灯来保证只有两个线程可以同时进行数据库调用: DWORD threadA(void* pD)
    {
    int iID=(int)pD;
    //在内部重新打开
    HANDLE hCounterIn=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sam sp 44"); for(int i=0;i<3;i++)
    {
    printf("%d wait for object\n",iID);
    WaitForSingleObject(hCounterIn,INFINITE);
    printf("\t\tthread %d : do database access call\n",iID);
    Sleep(100);
    printf("\t\tthread %d : do database access call end\n",iID);
    ReleaseSemaphore(hCounterIn,1,NULL);
    }
    CloseHandle(hCounterIn);
    return 0;
    }
    //in main function
    {
    //创建信号灯
    HANDLE hCounter=NULL;
    if( (hCounter=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
    {
    //如果没有其他进程创建这个信号灯,则重新创建
    hCounter = CreateSemaphore(NULL,2,2,"sam sp 44");
    } //创建线程
    HANDLE hThread[3];
    CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
    CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
    CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
    hThread[0]=pT1->m_hThread;
    hThread[1]=pT2->m_hThread;
    hThread[2]=pT3->m_hThread;
    //等待线程结束
    WaitForMultipleObjects(3,hThread,TRUE,INFINITE); //关闭句柄
    CloseHandle(hCounter);
    }信号灯有时用来作为计数器使用,一般来讲将其初始值设置为0,先调用ReleaseSemaphore来增加其计数,然后使用WaitForSingleObject来减小其计数,遗憾的是通常我们都不能得到信号灯的当前值,但是可以通过设置WaitForSingleObject的等待时间为0来检查信号灯当前是否为0。 接下来我们讲最后一种同步对象:事件,前面讲的信号灯和互斥量可以保证资源被正常的分配和使用,而事件是用来通知其他进程/线程某件操作已经完成。例如:现在有三个线程:threadA,threadB,threadC,现在要求他们中的部分功能要顺序执行,也就是说threadA执行完一部分后threadB执行,threadB执行完一部分后threadC开始执行。也许你觉得下面的代码可以满足要求: 要求:A1执行完后执行B2然后执行C3,再假设每个任务的执行时间都为1,而且允许并发操作。
    方案一:
    dword threadA(void*)
    {
    do something A1;
    create threadB;
    do something A2;
    do something A3;
    }dword threadB(void*)
    {
    do something B1;
    do something B2;
    create threadC;
    do something B3;
    }dword threadC(void*)
    {
    do something C1;
    do something C2;
    do something C3;
    }方案二:
    dword threadA(void*)
    {
    do something A1;
    do something A2;
    do something A3;
    }dword threadB(void*)
    {
    do something B1;
    wait for threadA end
    do something B2;
    do something B3;
    }dword threadC(void*)
    {
    do something C1;
    do something C2;
    wait for threadB end
    do something C3;
    }main()
    {
    create threadA;
    create threadB;
    create threadC;
    }方案三:
    dword threadA(void*)
    {
    do something A1;
    release event1;
    do something A2;
    do something A3;
    }dword threadB(void*)
    {
    do something B1;
    wait for envet1 be released
    do something B2;
    release event2;
    do something B3;
    }dword threadC(void*)
    {
    do something C1;
    do something C2;
    wait for event2 be released
    do something C3;
    }main()
    {
    create threadA;
    create threadB;
    create threadC;
    }
    比较一下三种方案的执行时间:
             方案一                        方案二                      方案三
    1 threadA  threadB  threadC    threadA  threadB  threadC   threadA  threadB  threadC
    2    A1                           A1        B1       C1       A1       B1       C1
    3    A2      B1                   A2                 C2       A2       B2       C2
    4    A1      B2                   A3                          A3       B3       C3
    5            B3         C1                  B2      
    6                       C2                  B3        
    7                       C3                           C3
    8                                                   可以看出来方案三的执行时间是最短的,当然这个例子有些极端,但我们可以看出事件对象用于通知其他进程/线程某件操作已经完成方面的作用是很大的,而且如果有的任务要在进程尖进行协调采用等待其他进程中线程结束的方式是不可能实现的。此外我也希望通过这个例子讲一点关于分析线程执行效率的方法。
      

  4.   

    一两种方式创建,一种为自动重置,在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象自动又变为无信号状态,一种为人工重置在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象状态不变。例如有多个线程都在等待一个线程运行结束,我们就可以使用人工重置事件,在被等待的线程结束时设置该事件为有信号状态,这样其他的多个线程对该事件的等待都会成功(因为该事件的状态不会被自动重置)。事件相关的API如下: 创建事件对象:
    HANDLE CreateEvent(
      LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全属性,NULL表示使用默认的安全描述
      BOOL bManualReset,  // 是否为人工重置
      BOOL bInitialState, // 初始状态是否为有信号状态
      LPCTSTR lpName      // 名字
    );
    打开事件对象:
    HANDLE OpenEvent(
      DWORD dwDesiredAccess,  // 存取方式
      BOOL bInheritHandle,    // 是否能够被继承
      LPCTSTR lpName          // 名字
    );
    设置事件为无信号状态:
    BOOL ResetEvent(
      HANDLE hEvent   // 句柄
    );
    设置事件有无信号状态:
    BOOL SetEvent(
      HANDLE hEvent   // 句柄
    );
    关闭事件对象:
    BOOL CloseHandle(
      HANDLE hObject   // 句柄
    );下面的代码演示了自动重置和人工重置事件在使用中的不同效果: DWORD threadA(void* pD)
    {
    int iID=(int)pD;
    //在内部重新打开
    HANDLE hCounterIn=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44"); printf("\tthread %d begin\n",iID);
    //设置成为有信号状态
    Sleep(1000);
    SetEvent(hCounterIn);
    Sleep(1000);
    printf("\tthread %d end\n",iID);
    CloseHandle(hCounterIn);
    return 0;
    }DWORD threadB(void* pD)
    {//等待threadA结束后在继续执行
    int iID=(int)pD;
    //在内部重新打开
    HANDLE hCounterIn=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44"); if(WAIT_TIMEOUT == WaitForSingleObject(hCounterIn,10*1000))
    {
    printf("\t\tthread %d wait time out\n",iID);
    }
    else
    {
    printf("\t\tthread %d wait ok\n",iID);
    }
    CloseHandle(hCounterIn);
    return 0;
    }//in main function
    {
    HANDLE hCounter=NULL;
    if( (hCounter=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
    {
    //如果没有其他进程创建这个事件,则重新创建,该事件为人工重置事件
    hCounter = CreateEvent(NULL,TRUE,FALSE,"sam sp 44");
    } //创建线程
    HANDLE hThread[3];
    printf("test of manual rest event\n");
    CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
    CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadB,(void*)2);
    CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadB,(void*)3);
    hThread[0]=pT1->m_hThread;
    hThread[1]=pT2->m_hThread;
    hThread[2]=pT3->m_hThread;
    //等待线程结束
    WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
    //关闭句柄
    CloseHandle(hCounter); if( (hCounter=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
    {
    //如果没有其他进程创建这个事件,则重新创建,该事件为自动重置事件
    hCounter = CreateEvent(NULL,FALSE,FALSE,"sam sp 44");
    }
    //创建线程
    printf("test of auto rest event\n");
    pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
    pT2=AfxBeginThread((AFX_THREADPROC)threadB,(void*)2);
    pT3=AfxBeginThread((AFX_THREADPROC)threadB,(void*)3);
    hThread[0]=pT1->m_hThread;
    hThread[1]=pT2->m_hThread;
    hThread[2]=pT3->m_hThread;
    //等待线程结束
    WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
    //关闭句柄
    CloseHandle(hCounter);
    } 从执行结果中我们可以看到在第二次执行时由于使用了自动重置事件threadB中只有一个线程能够等待到threadA中释放的事件对象。 在处理多进程/线程的同步问题时必须要小心避免发生死锁问题,比如说现在有两个互斥量A和B,两个线程tA和tB,他们在执行前都需要得到这两个互斥量,但现在这种情况发生了,tA拥有了互斥量A,tB拥有了互斥量B,但它们同时都在等待拥有另一个互斥量,这时候显然谁也不可能得到自己希望的资源。这种互相拥有对方所拥有的资源而且都在等待对方拥有的资源的情况就称为死锁。关于这个问题更详细的介绍请参考其他参考书。 在MFC中对于各种同步对象都提供了相对应的类在这些类中封装了上面介绍的对象创建,打开,控制,删除功能。但是如果要使用等待功能则需要使用另外两个类:CSingleLock和CMultiLock。这两个类中封装了WaitForSingleObject和WaitForMultipleObjects函数。如果大家觉的需要可以看看这些类的定义,我想通过上面的介绍可以很容易理解,但是在对象同步问题上我觉得使用API函数比使用MFC类更为直观和方便。