📄 chap12_2.htm
字号:
</td> <td width="65%"> <p align="JUSTIFY">若bWaitAll为TRUE,则返回值表明所有对象都有信号,但有一个mutex被放弃了。若bWaitAll为FALSE,则返回值减去WAIT_ABANDONED_0就是被放弃mutex在对象数组中的索引。 </td> </tr> <tr> <td width="35%"> <p align="JUSTIFY">WAIT_TIMEOUT </td> <td width="65%"> <p align="JUSTIFY">超时返回。 </td> </tr> </table> <p><b> </b></p> <p align="JUSTIFY"> <b></b><font color="#3973DE" face="Times New Roman">12.2.3 </font><font color="#3973DE">同步对象</font></p> <p align="JUSTIFY"> 同步对象用来协调多线程的执行,它可以被多个线程共享。线程的等待函数用同步对象的句柄作为参数,同步对象应该是所有要使用的线程都能访问到的。同步对象的状态要么是有信号的,要么是无信号的。同步对象主要有三种:事件、mutex和信号灯。</p> <p align="JUSTIFY"> 事件对象(Event)是最简单的同步对象,它包括有信号和无信号两种状态。在线程访问某一资源之前,也许需要等待某一事件的发生,这时用事件对象最合适。例如,只有在通信端口缓冲区收到数据后,监视线程才被激活。</p> <p align="JUSTIFY"> 事件对象是用CreateEvent函数建立的。该函数可以指定事件对象的种类和事件的初始状态。如果是手工重置事件,那么它总是保持有信号状态,直到用ResetEvent函数重置成无信号的事件。如果是自动重置事件,那么它的状态在单个等待线程释放后会自动变为无信号的。用SetEvent可以把事件对象设置成有信号状态。在建立事件时,可以为对象起个名字,这样其它进程中的线程可以用OpenEvent函数打开指定名字的事件对象句柄。</p> <p align="JUSTIFY"> mutex对象的状态在它不被任何线程拥有时是有信号的,而当它被拥有时则是无信号的。mutex对象很适合用来协调多个线程对共享资源的互斥访问(mutually exclusive)。</p> <p align="JUSTIFY"> 线程用CreateMutex函数来建立mutex对象,在建立mutex时,可以为对象起个名字,这样其它进程中的线程可以用OpenMutex函数打开指定名字的mutex对象句柄。在完成对共享资源的访问后,线程可以调用ReleaseMutex来释放mutex,以便让别的线程能访问共享资源。如果线程终止而不释放mutex,则认为该mutex被废弃。</p> <p align="JUSTIFY"> 信号灯对象维护一个从0开始的计数,在计数值大于0时对象是有信号的,而在计数值为0时则是无信号的。信号灯对象可用来限制对共享资源进行访问的线程数量。线程用CreateSemaphore函数来建立信号灯对象,在调用该函数时,可以指定对象的初始计数和最大计数。在建立信号灯时也可以为对象起个名字,别的进程中的线程可以用OpenSemaphore函数打开指定名字的信号灯句柄。</p> <p align="JUSTIFY"> 一般把信号灯的初始计数设置成最大值。每次当信号灯有信号使等待函数返回时,信号灯计数就会减1,而调用ReleaseSemaphore可以增加信号灯的计数。计数值越小就表明访问共享资源的程序越多。</p> <p align="JUSTIFY"> 除了上述三种同步对象外,表12.3中的对象也可用于同步。另外,有时可以用文件或通信设备作为同步对象使用。</p> <p align="JUSTIFY"><b> </b></p> <b> <p align="CENTER">表12.3 可用于同步的对象</p> </b> <table border="1" cellspacing="1" cellpadding="1" width="579"> <tr> <td width="22%"><b> <p align="JUSTIFY">对象 </b></td> <td width="78%"><b> <p align="JUSTIFY">描述 </b></td> </tr> <tr> <td width="22%"> <p align="JUSTIFY">变化通知 </td> <td width="78%"> <p align="JUSTIFY">由FindFirstChangeNotification函数建立,当在指定目录中发生指定类型的变化时对象变成有信号的。 </td> </tr> <tr> <td width="22%"> <p align="JUSTIFY">控制台输入 </td> <td width="78%"> <p align="JUSTIFY">在控制台建立是被创建。它是用CONIN$调用CreateFile函数返回的句柄,或是GetStdHandle函数的返回句柄。如果控制台输入缓冲区中有数据,那么对象是有信号的,如果缓冲区为空,则对象是无信号的。 </td> </tr> <tr> <td width="22%"> <p align="JUSTIFY">进程 </td> <td width="78%"> <p align="JUSTIFY">当调用CreateProcess建立进程时被创建。进程在运行时对象是无信号的,当进程终止时对象是有信号的。 </td> </tr> <tr> <td width="22%"> <p align="JUSTIFY">线程 </td> <td width="78%"> <p align="JUSTIFY">当调用Createprocess、CreateThread或CreateRemoteThread函数创建新线程时被创建。在线程运行是对象是无信号的,在线程终止时则是有信号的。 </td> </tr> </table> <p> </p> <p align="JUSTIFY"> </p> <p align="JUSTIFY"> 当对象不再使用时,应该用CloseHandle函数关闭对象句柄。</p> <p align="JUSTIFY"> 清单12.3是一个使用事件对象的简单例子,在该例中,假设主线程要读取共享缓冲区中的内容,而辅助线程负责向缓冲区中写入数据。两个线程使用了一个hEvent事件对象来同步。在用CreateEvent函数创建事件对象句柄时,指定该对象是一个自动重置事件,其初始状态为有信号的。当线程要读写缓冲区时,调用WaitForSingleObject函数无限等待hEvent信号。如果hEvent无信号,则说明另一线程正在访问缓冲区;如果有信号,则本线程可以访问缓冲区,WaitForSingleObject函数在返回后会自动把hEvent置成无信号的,这样在本线程读写缓冲区时别的线程不会同时访问。在完成读写操作后,调用SetEvent函数把hEvent置成有信号的,以使别的线程有机会访问共享缓冲区。</p> <b> <p align="JUSTIFY"> </p> </b> <p align="JUSTIFY"><b>清单12.3 </b>使用事件对象的简单例子</p> <p align="JUSTIFY">HANDLE hEvent; //全局变量</p> <p align="JUSTIFY"> </p> <p align="JUSTIFY">//主线程</p> <p align="JUSTIFY">hEvent=CreateEvent(NULL, FALSE, TRUE, NULL);</p> <p align="JUSTIFY">if(hEvent= =NULL) return;</p> <p><b> </b></p> <b> <p align="JUSTIFY">. . .</p> </b> <p align="JUSTIFY">WaitForSingleObject(hEvent, INFINITE);</p> <p align="JUSTIFY">ReadFromBuf( );</p> <p align="JUSTIFY">SetEvent( hEvent );</p> <p><b> </b></p> <b> <p align="JUSTIFY">. . .</p> </b> <p align="JUSTIFY">CloseHandle( hEvent );</p> <p align="JUSTIFY"> </p> <p align="JUSTIFY"> </p> <p align="JUSTIFY">//辅助线程</p> <p align="JUSTIFY">UINT MyThreadProc( LPVOID pParam )</p> <p align="JUSTIFY">{</p> <p align="JUSTIFY"><b>. . .</b></p> <p align="JUSTIFY">WaitForSingleObject(hEvent, INFINITE);</p> <p align="JUSTIFY">WriteToBuf( );</p> <p align="JUSTIFY">SetEvent( hEvent );</p> <p align="JUSTIFY"><b>. . .</b></p> <p align="JUSTIFY"> <b></b>return 0; // 线程正常结束</p> <p align="JUSTIFY">}</p> <p align="JUSTIFY"><b></b><font color="#3973DE" face="Times New Roman">12.2.4 </font><font color="#3973DE">关键节和互锁变量访问</font></p> <p align="JUSTIFY"> 关键节(Critical Seciton)与mutex的功能类似,但它只能由同一进程中的线程使用。关键节可以防止共享资源被同时访问。</p> <p align="JUSTIFY"> 进程负责为关键节分配内存空间,关键节实际上是一个CRITICAL_SECTION型的变量,它一次只能被一个线程拥有。在线程使用关键节之前,必须调用InitializeCriticalSection函数将其初始化。如果线程中有一段关键的代码不希望被别的线程中断,那么可以调用EnterCriticalSection函数来申请关键节的所有权,在运行完关键代码后再用LeaveCriticalSection函数来释放所有权。如果在调用EnterCriticalSection时关键节对象已被另一个线程拥有,那么该函数将无限期等待所有权。</p> <p align="JUSTIFY"> 利用互锁变量可以建立简单有效的同步机制。使用函数InterlockedIncrement和InterlockedDecrement可以增加或减少多个线程共享的一个32位变量的值,并且可以检查结果是否为0。线程不必担心会被其它线程中断而导致错误。如果变量位于共享内存中,那么不同进程中的线程也可以使用这种机制。</p> <div align="center"> <center> <table border="0" cellpadding="0" cellspacing="0" width="615"> <tr> <td><a href="chap12_1.htm">上一页</a></td> <td> <p align="right"><a href="chap12_3.htm">下一页</a> </td> </tr> </table> </td> </tr> </table> </center></div></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -