📄 ch27.htm
字号:
<P>This function's two arguments are:</P>
<ul>
<li> The handle of the event for which to check (stored in the event object's <font color="#008000">m_hObject</font> data member)</P>
<li> How long the function should wait for the event</P>
</ul>
<P>The predefined <font color="#008000">INFINITE</font> constant tells <font color="#008000">WaitForSingleObject()</font> not to return until the specified event is signaled. In other words, if you place the preceding line at the beginning of your thread,
the system suspends the thread until the event is signaled. Even though you've started the thread execution, it's halted until whatever you need to have happen happens. When your program is ready for the thread to perform its duty, you call the <font
color="#008000">SetEvent()</font> function, as described a couple of paragraphs ago.</P>
<P>Once the thread is no longer suspended, it can go about its business. But, if you want to signal the end of the thread from the main program, the thread must watch for this next event to be signaled. The thread can do this by polling for the event. To
poll for the event, you again call <font color="#008000">WaitForSingleObject()</font>, only this time you give the function a wait time of 0, like this:</P>
<pre><font color="#008000">::WaitForSingleObject(threadend.m_hObject, 0);</font></pre>
<P>In this case, if <font color="#008000">WaitForSingleObject()</font> returns <font color="#008000">WAIT_OBJECT_0</font>, the event has been signaled. Otherwise, the event is still in its nonsignaled state.</P>
<P>To better see how event objects work, follow these steps to further modify the Thread application:</P>
<ol>
<li><P> Add the following line to the top of the ThreadView.cpp file, right after the line <font color="#008000">#include "ThreadView.h"</font>:</P>
<pre><font color="#008000">#include "afxmt.h"</font></pre>
<li><P> Add the following lines near the top of the ThreadView.cpp file, after the <font color="#008000">volatile int threadController</font> line that you placed there previously:</P>
<pre><font color="#008000">CEvent threadStart;</font></pre>
<pre><font color="#008000">CEvent threadEnd;</font></pre>
<li><P> Delete the <font color="#008000">volatile int threadController</font> line from the file.</P>
<li><P> Replace the <font color="#008000">ThreadProc()</font> function with the one shown in Listing 27.5.</P>
<P><I>Listing 27.5—Yet Another </I>ThreadProc()</P>
<pre><font color="#008000">UINT ThreadProc(LPVOID param)</font></pre>
<pre><font color="#008000">{</font></pre>
<pre><font color="#008000"> ::WaitForSingleObject(threadStart.m_hObject, INFINITE);</font></pre>
<pre><font color="#008000"> ::MessageBox((HWND)param, "Thread activated.",</font></pre>
<pre><font color="#008000"> "Thread", MB_OK);</font></pre>
<pre><font color="#008000"> BOOL keepRunning = TRUE;</font></pre>
<pre><font color="#008000"> while (keepRunning)</font></pre>
<pre><font color="#008000"> {</font></pre>
<pre><font color="#008000"> int result =</font></pre>
<pre><font color="#008000"> ::WaitForSingleObject(threadEnd.m_hObject, 0);</font></pre>
<pre><font color="#008000"> if (result == WAIT_OBJECT_0)</font></pre>
<pre><font color="#008000"> keepRunning = FALSE;</font></pre>
<pre><font color="#008000"> }</font></pre>
<pre><font color="#008000"> ::PostMessage((HWND)param, WM_THREADENDED, 0, 0);</font></pre>
<pre><font color="#008000"> return 0;</font></pre>
<pre><font color="#008000">}</font></pre>
<li><P> Replace all of the code in the <font color="#008000">OnStartthread()</font> function with the following line:</P>
<pre><font color="#008000"> threadStart.SetEvent();</font></pre>
<li><P> Replace the code in the <font color="#008000">OnStopthread()</font> function with the following line:</P>
<pre><font color="#008000"> threadEnd.SetEvent();</font></pre>
<li><P> Use ClassWizard to add an <font color="#008000">OnCreate()</font> function that handles the <font color="#008000">WM_CREATE</font> message, as shown in Figure 27.8. Make sure that you have <font color="#008000">CThreadView</font> selected in the
Class <U>N</U>ame box before you add the function.</P>
</ol>
<A HREF="BBfig08.gif" tppabs="http://www.mcp.com/814147200/0-7897/0-7897-1145-1/figs/ch27/BBfig08.gif"><b>Fig. 27.8</b></A>
<P><I>Use ClassWizard to add the </I><I>OnCreate()</I><I> function.</I></P>
<ol start=8>
<li><P> Add the following lines to the <font color="#008000">OnCreate()</font> function, replacing the <font color="#008000">TODO: Add your specialized creation code here</font> comment:</P>
<pre><font color="#008000"> HWND hWnd = GetSafeHwnd();</font></pre>
<pre><font color="#008000"> AfxBeginThread(ThreadProc, hWnd);</font></pre>
</ol>
<P>Again, this new version of the program seems to run just like the previous version. However, the program is now using both event objects and user-defined Windows messages to communicate between the main program and the thread. No more messing with
clunky global variables.</P>
<P>One big difference from previous versions of the program is that the secondary thread gets started in the <font color="#008000">OnCreate()</font> function, which is called when the application first runs and creates the view. However, because the first
line of the thread function is the call to <font color="#008000">WaitForSingleObject()</font>, the thread immediately suspends execution and waits for the <font color="#008000">threadStart</font> event to be signaled.</P>
<P>When the <font color="#008000">threadStart</font> event object is signaled, the thread is free to display the message box and then enter its <font color="#008000">while</font> loop, where it polls the <font color="#008000">threadEnd</font> event
object. The <font color="#008000">while</font> loop continues to execute until <font color="#008000">threadEnd</font> is signaled, at which time the thread sends the <font color="#008000">WM_THREADENDED</font> message to the main program and exits. Because
the thread is started in <font color="#008000">OnCreate()</font>, once the thread ends, it cannot be restarted.</P>
<H3><B>Using Thread Synchronization</B></H3>
<P>Using multiple threads can lead to some interesting problems. For example, how do you prevent two threads from accessing the same data at the same time? What if, for example, one thread is in the middle of trying to update a data set when another
thread tries to read that data? The second thread will almost certainly read corrupted data, since only some of the data set will have been updated.</P>
<P>Trying to keep threads working together properly is called <I>thread </I><I>synchronization</I>. Event objects, about which you just learned, are actually a form of thread synchronization. In this section, you'll learn about <I>critical sections,
mutexes,</I> and <I>semaphores</I>—thread synchronization objects that make your thread programming even safer.</P>
<P><B>Using Critical Sections</B></P>
<P>Critical sections are an easy way to ensure that only one thread at a time can access a data set. When you use a critical section, you give your threads an object that they have to share between them. Whichever thread possesses the critical-section
object has access to the guarded data. Other threads have to wait until the first thread releases the critical section, after which another thread can grab the critical section in order to access the data in turn.</P>
<P>Because the guarded data is represented by a single critical-section object, and because only one thread can own the critical section at any given time, the guarded data can never be accessed by more than a single thread at a time.</P>
<P>To create a critical-section object in an MFC program, you create an instance of the <font color="#008000">CCriticalSection</font> class, like this:</P>
<pre><font color="#008000">CCriticalSection criticalSection;</font></pre>
<P>Then, when program code is about to access the data that you want to protect, you call the critical-section object's <font color="#008000">Lock()</font> member function, like this:</P>
<pre><font color="#008000">criticalSection.Lock();</font></pre>
<P>If another thread doesn't already own the critical section, <font color="#008000">Lock()</font> gives the object to the calling thread. That thread can then access the guarded data, after which it calls the critical-section object's <font
color="#008000">Unlock()</font> member function:</P>
<pre><font color="#008000">criticalSection.Unlock();</font></pre>
<p><font color="#008000">Unlock()</font> releases the ownership of the critical-section object so that another thread can grab it and access the guarded data.</P>
<P>The best way to implement something like critical sections is to build the data you want to protect into a thread-safe class. When you do this, you no longer have to worry about thread synchronization in the main program; the class handles it all for
you. As an example, look at Listing 27.6, which is the header file for a thread-safe array class.</P>
<P><I>Listing 27.6—COUNTARRAY.H—The </I>CCountArray<I> Class's Header File</I></P>
<pre><font color="#008000">#include "afxmt.h"</font></pre>
<pre><font color="#008000">class CCountArray</font></pre>
<pre><font color="#008000">{</font></pre>
<pre><font color="#008000">private:</font></pre>
<pre><font color="#008000"> int array[10];</font></pre>
<pre><font color="#008000"> CCriticalSection criticalSection;</font></pre>
<pre><font color="#008000">public:</font></pre>
<pre><font color="#008000"> CCountArray() {};</font></pre>
<pre><font color="#008000"> ~CCountArray() {};</font></pre>
<pre><font color="#008000"> void SetArray(int value);</font></pre>
<pre><font color="#008000"> void GetArray(int dstArray[10]);</font></pre>
<pre><font color="#008000">};</font></pre>
<P>The header file starts off by including the MFC header file, afxmt.h, which gives the program access to the <font color="#008000">CCriticalSection</font> class. Within the <font color="#008000">CCountArray</font> class declaration, the file declares a
ten-element integer array, which is the data that the critical section will guard, and declares the critical-section object, here called <font color="#008000">criticalSection</font>. The <font color="#008000">CCountArray</font> class's public member
functions include the usual constructor and destructor, as well as functions for setting and reading the array. It's these latter two member functions that must deal with the critical-section object, because it's those functions that access the array.</P>
<P>Listing 27.7 is the <font color="#008000">CCountArray</font> class's implementation file. Notice that, in each member function, the class takes care of locking and unlocking the critical-section object. This means that any thread can call these member
functions without worrying about thread synchronization. For example, if thread one calls <font color="#008000">SetArray()</font>, the first thing <font color="#008000">SetArray()</font> does is call <font color="#008000">criticalSection.Lock()</font>,
which gives the critical-section object to thread one. The complete <font color="#008000">for</font> loop then executes, without any fear of being interrupted by another thread. If thread two calls <font color="#008000">SetArray()</font> or <font
color="#008000">GetArray()</font>, the call to <font color="#008000">criticalSection.Lock()</font> suspends thread two until thread one releases the critical-section object, which it does when <font color="#008000">SetArray()</font> finishes the <font
color="#008000">for</font> loop and executes the <font color="#008000">criticalSection.Unlock()</font> line. Then the system wakes up thread two and gives it the critical-section object. In this way, all threads have to wait politely for their chance to
access the guarded data.</P>
<P><I>Listing 27.7—COUNTARRAY.CPP—The </I>CCountArray<I> Class's Implementation File</I></P>
<pre><font color="#008000">#include "stdafx.h"</font></pre>
<pre><font color="#008000">#include "CountArray.h"</font></pre>
<pre><font color="#008000">void CCountArray::SetArray(int value)</font></pre>
<pre><font color="#008000">{</font></pre>
<pre><font color="#008000"> criticalSection.Lock();</font></pre>
<pre><font color="#008000"> for (int x=0; x<10; ++x)</font></pre>
<pre><font color="#008000"> array[x] = value;</font></pre>
<pre><font color="#008000"> criticalSection.Unlock();</font></pre>
<pre><font color="#008000">}</font></pre>
<pre><font color="#008000">void CCountArray::GetArray(int dstArray[10])</font></pre>
<pre><font color="#008000">{</font></pre>
<pre><font color="#008000"> criticalSection.Lock();</font></pre>
<pre><font color="#008000"> for (int x=0; x<10; ++x)</font></pre>
<pre><font color="#008000"> dstArray[x] = array[x];</font></pre>
<pre><font color="#008000"> criticalSection.Unlock();</font></pre>
<pre><font color="#008000">}</font></pre>
<P>Now that you've had a chance to see what a thread-safe class looks like, it's time to put the class to work. Perform the following steps, which modify the Thread application to test the <font color="#008000">CCountArray</font> class:</P>
<ol>
<li><P> Use the File, New command to add a new C++ header file called CountArray.h to the project, as shown in Figure 27.9. Enter the code from Listing 27.6.</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -