📄 chap12.html
字号:
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>::WaitForSingleObject(threadend.m_hObject, 0);</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">在这种情况下,如果</FONT><FONT SIZE=3>WaitForSingleObject()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>返回值为</FONT><FONT SIZE=3>WAIT_OBJECT_0,</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>则</FONT><FONT SIZE=3>CEvent</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象处在通信状态。否则,</FONT><FONT SIZE=3>CEvent</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象处在非通信状态。</P>
<P ALIGN="JUSTIFY">下面的例子说明如何使用</FONT><FONT SIZE=3>CEvent</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类在两个线程间通信。按照以下步骤进行:</P>
</FONT><FONT SIZE=3><P ALIGN="JUSTIFY">1.	</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>在</FONT><FONT SIZE=3>ThreadView.cpp</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中</FONT><FONT SIZE=3>#include "ThreadView.h"</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>语句后面加上</FONT><FONT SIZE=3>#include "afxmt.h"</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>。</P>
</FONT><FONT SIZE=3><P ALIGN="JUSTIFY">2.	</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>在</FONT><FONT SIZE=3>ThreadView.cpp</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中</FONT><FONT SIZE=3>volatile int threadController</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>语句后加上下列语句:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>CEvent threadStart;</P>
<P>CEvent threadEnd;</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">删除语句</FONT><FONT SIZE=3>volatile int threadController</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>。</P>
</FONT><FONT SIZE=3><P ALIGN="JUSTIFY">3.	</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>用下面的代码更换</FONT><FONT SIZE=3>ThreadProc()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>函数。</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>UINT ThreadProc(LPVOID param)</P>
<P>{</P>
<P> ::WaitForSingleObject(threadStart.m_hObject, INFINITE);</P>
<P> ::MessageBox((HWND)param, "Thread activated.", "Thread", MB_OK);</P>
<P> BOOL keepRunning = TRUE;</P>
<P> while (keepRunning)</P>
<P> {</P>
<P> int result = ::WaitForSingleObject(threadEnd.m_hObject, 0);</P>
<P> if (result == WAIT_OBJECT_0)</P>
<P> keepRunning = FALSE;</P>
<P> }</P>
<P> ::PostMessage((HWND)param, WM_THREADENDED, 0, 0);</P>
<P> return 0;</P>
<P>}</P>
</FONT><FONT SIZE=3><P ALIGN="JUSTIFY">4.	</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>用下面的语句替换</FONT><FONT SIZE=3>OnStartthread()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>函数中的内容。</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>threadStart.SetEvent();</P>
</FONT><FONT SIZE=3><P ALIGN="JUSTIFY">5.	</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>用下面的语句替换</FONT><FONT SIZE=3>OnStopthread()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>函数中的内容。</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>threadEnd.SetEvent();</P>
</FONT><FONT SIZE=3><P ALIGN="JUSTIFY">6.	</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>使用</FONT><FONT SIZE=3>ClassWizard</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>为</FONT><FONT SIZE=3>CthreadView</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>处理</FONT><FONT SIZE=3>WM_CREATE</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>消息的函数</FONT><FONT SIZE=3>OnCreate()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,并在</FONT><FONT SIZE=3>TODO</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>后面添加代码。</FONT><FONT SIZE=3>OnCreate()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>函数如下所示:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>int CThreadView::OnCreate(LPCREATESTRUCT lpCreateStruct) </P>
<P>{</P>
<P>	if (CView::OnCreate(lpCreateStruct) == -1)</P>
<P>		return -1;</P>
<P>	</P>
<P>	// TODO: Add your specialized creation code here</P>
<P>	HWND hWnd = GetSafeHwnd();</P>
<P> AfxBeginThread(ThreadProc, hWnd);</P>
<P>	return 0;</P>
<P>}</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">编译并运行这个程序,新版本的程序运行起来和旧版本的程序一样,但是,新版本的程序为了实现在主线程和次要线程间通信,既使用了</FONT><FONT SIZE=3>CEvent</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类,又使用了用户定义的</FONT><FONT SIZE=3>Windows</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>消息。</P>
<P ALIGN="JUSTIFY">新版本的程序和旧版本的程序的一个大的不同在于次要线程在</FONT><FONT SIZE=3>OnCreate()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>函数中被启动。然而由于线程函数的第一行即调用</FONT><FONT SIZE=3>WaitForSingleObject()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,所以此线程立即被挂起并且等待</FONT><FONT SIZE=3>threadStart</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>处于通信状态。</P>
<P ALIGN="JUSTIFY">当</FONT><FONT SIZE=3>threadStart</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>处在通信状态时,新线程显示消息框,然后进入</FONT><FONT SIZE=3>while</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>循环。这个</FONT><FONT SIZE=3>while</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>循环继续执行直到</FONT><FONT SIZE=3>threadEnd</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>处在通信状态,然后线程向主线程发送一个</FONT><FONT SIZE=3>WM_THREADENDED</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>消息并退出。因为此线程是在</FONT><FONT SIZE=3>OnCreate()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>函数中被创建的</FONT><FONT SIZE=3>,</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>一旦结束,不会被重新启动。</P>
<P ALIGN="JUSTIFY"> </P>
</FONT><FONT FACE="仿宋_GB2312" LANG="ZH-CN" SIZE=4><P ALIGN="CENTER"><A NAME="_Toc425699149">第三节</FONT><FONT SIZE=4> </FONT><FONT FACE="仿宋_GB2312" LANG="ZH-CN" SIZE=4>线程同步</A></P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">使用多线程可以带来一些非常有趣的问题。例如,如何防止两个线程在同一时间访问同一数据?例如,假设一个线程正在更新一个数据集,而同时另外一个线程正在读取数据集,结果如何?第二个线程将会读取到错误的数据,因为数据集中只有一部分元素被更新过。</P>
<P ALIGN="JUSTIFY">保持在同一个进程内的线程工作协调一致称之为线程同步。</FONT><FONT SIZE=3>Event</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象实际上就是线程同步的一种形式。在本节中,你将会学到三种使你的多线程程序更安全的线程同步对象</FONT><FONT SIZE=3>—critical section</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>、互斥对象(</FONT><FONT SIZE=3>mutex</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>)、信号量(</FONT><FONT SIZE=3>semaphore</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>)。</P><DIR>
</FONT><FONT FACE="Arial" SIZE=3><P>(1)	</FONT><FONT FACE="黑体" LANG="ZH-CN" SIZE=3>使用</FONT><FONT FACE="Arial" SIZE=3>Critical Section</P></DIR>
</FONT><FONT SIZE=3><P ALIGN="JUSTIFY">Critical Section</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>是一种保证在一个时间只有一个线程访问数据集的非常简单的方法。当你使用</FONT><FONT SIZE=3>Critical Section</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,你给了线程一个它们必须共享的对象。任何拥有</FONT><FONT SIZE=3>Critical Section</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象的线程可以访问被保护起来的数据。其它线程必须等待直到第一个线程释放了</FONT><FONT SIZE=3>Critical Section</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象,此后其它线程可以按照顺序抢占</FONT><FONT SIZE=3>Critical Section</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象,访问数据。</P>
<P ALIGN="JUSTIFY">因为线程只有拥有</FONT><FONT SIZE=3>Critical Section</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象才能访问数据,而且在一个时刻只有一个线程可以拥有</FONT><FONT SIZE=3>Critical Section</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象,所以决不会出现一个时刻有多个线程访问数据。</P>
<P ALIGN="JUSTIFY">为了在</FONT><FONT SIZE=3>MFC</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>程序中创建一个</FONT><FONT SIZE=3>Critical Section</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象,你应当创建</FONT><FONT SIZE=3>CcriticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类的对象,如下所示:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>CCriticalSection criticalSection</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">然后,当程序代码准备访问你保护的数据时,你应当调用</FONT><FONT SIZE=3>CCriticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>的成员函数</FONT><FONT SIZE=3>Lock()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>criticalSection.Lock();</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">如果另外一个线程并没有拥有</FONT><FONT SIZE=3>criticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,</FONT><FONT SIZE=3>Lock()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>将</FONT><FONT SIZE=3>criticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>给调用它的线程。这个线程便能够访问受保护的数据,此后它调用</FONT><FONT SIZE=3>CcriticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>的成员函数</FONT><FONT SIZE=3>Unlock()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>criticalSection.Unlock();</P>
</FONT><FONT SIZE=3><P ALIGN="JUSTIFY">Unlock()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>释放了对</FONT><FONT SIZE=3>criticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>的拥有权,这样其它线程就可以占有它并访问受保护的数据。</P>
<P ALIGN="JUSTIFY">最好的方法是将数据放在线程安全类中。当你这样做后,你不用担心在主线程中的线程同步,线程安全类会替你处理的。下面的类</FONT><FONT SIZE=3>CCountArray</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>便是一个线程安全类。</P>
<P ALIGN="JUSTIFY">以下是</FONT><FONT SIZE=3>COUNTARRAY.H</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,</FONT><FONT SIZE=3>CcountArray</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>的头文件。</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>#include "afxmt.h"</P>
<P>class CCountArray</P>
<P>{</P>
<P>private:</P>
<P> int array[10];</P>
<P> CCriticalSection criticalSection;</P>
<P>public:</P>
<P> CCountArray() {};</P>
<P> ~CCountArray() {};</P>
<P> void SetArray(int value);</P>
<P> void GetArray(int dstArray[10]);</P>
<P>};</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">在该头文件中包含一个</FONT><FONT SIZE=3>MFC</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>的头文件</FONT><FONT SIZE=3>afxmt.h</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,以使程序可以使用</FONT><FONT SIZE=3>CCriticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类。在</FONT><FONT SIZE=3>CCountArray</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类的声明中,头文件声明了一个十个元素的整形数组,这是</FONT><FONT SIZE=3>CCriticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类的对象将要保护的数据,并且声明了一个</FONT><FONT SIZE=3>CCriticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类的对象</FONT><FONT SIZE=3>criticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>。</FONT><FONT SIZE=3>CCountArray</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类的公共成员函数包含构造和析购函数。后面两个成员函数用于访问数据。</P>
<P ALIGN="JUSTIFY">下面是</FONT><FONT SIZE=3>CCountArray</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类的执行文件。注意,在每一个成员函数中,</FONT><FONT SIZE=3>CCountArray</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>都在密切关注着</FONT><FONT SIZE=3>CCriticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类的对象的状态。这也意味这任何调用这些成员函数的线程不必担心线程同步。例如,如果线程</FONT><FONT SIZE=3>1</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>调用了</FONT><FONT SIZE=3>SetArray(),SetArray()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>所做的第一件事就是调用</FONT><FONT SIZE=3>criticalSection.Lock(),</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>这将把</FONT><FONT SIZE=3>criticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>给线程</FONT><FONT SIZE=3>1</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,此后可以完成一个循环而不用担心被其它线程打断。如果线程</FONT><FONT SIZE=3>2</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>调用了</FONT><FONT SIZE=3>SetArray()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>或</FONT><FONT SIZE=3>GetArray(),criticalSection.Lock()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>语句将挂起线程</FONT><FONT SIZE=3>2</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>直到线程</FONT><FONT SIZE=3>1</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>完成循环,执行</FONT><FONT SIZE=3>criticalSection.Unlock()</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>语句将对</FONT><FONT SIZE=3>criticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>的拥有权释放。这时系统唤醒线程</FONT><FONT SIZE=3>2</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,并将</FONT><FONT SIZE=3>criticalSection</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>给它。通过这种方式,所有线程必须安静的等待它们访问数据的机会到来。</P>
<P ALIGN="JUSTIFY">下面是</FONT><FONT SIZE=3>COUNTARRAY.CPP</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,</FONT><FONT SIZE=3>CcountArray</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类的执行文件。</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>#include "stdafx.h"</P>
<P>#include "CountArray.h"</P>
<P>void CCountArray::SetArray(int value)</P>
<P>{</P>
<P> criticalSection.Lock();</P>
<P> for (int x=0; x<10; ++x)</P>
<P> array[x] = value;</P>
<P> criticalSection.Unlock();</P>
<P>}</P>
<P>void CCountArray::GetArray(int dstArray[10])</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -