📄 ch27.htm
字号:
<BLOCKQUOTE>
<PRE> threadStart.SetEvent();</PRE>
</BLOCKQUOTE>
<PRE></PRE>
<DL>
<DT></DT>
<DD><B>6. </B>Replace the code in the OnStopthread() function with the following
line:
<P>
</DL>
<BLOCKQUOTE>
<PRE> threadEnd.SetEvent();</PRE>
</BLOCKQUOTE>
<PRE></PRE>
<DL>
<DT></DT>
<DD><B>7.</B> Use ClassWizard to add an OnCreate() function that handles the WM_CREATE
message, as shown in Figure 27.8. Make sure that you have <B>CThreadView</B> selected
in the Class Name box before you add the function.
<P>
</DL>
<P><A HREF="javascript:popUp('27uvc08.gif')"><B>FIG. 27.8</B></A><B> </B><I>Use ClassWizard
to add the OnCreate() function.</I></P>
<DL>
<DT><I></I></DT>
<DD><B>8. </B>Add the following lines to the OnCreate() function, replacing the TODO:
Add your specialized creation code here comment:
<P>
</DL>
<PRE> HWND hWnd = GetSafeHwnd();
AfxBeginThread(ThreadProc, hWnd);
</PRE>
<P>Again, this new version of the program seems to run just like the preceding 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 is begun in the OnCreate() 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 WaitForSingleObject(), the thread immediately suspends execution and
waits for the threadStart event to be signaled.</P>
<P>When the threadStart event object is signaled, the thread is free to display the
message box and then enter its while loop, where it polls the threadEnd event object.
The while loop continues to execute until threadEnd is signaled, at which time the
thread sends the WM_THREADENDED message to the main program and exits. Because the
thread is started in OnCreate(), after the thread ends, it can't be restarted.</P>
<P>
<H2><A NAME="Heading6"></A>Using Thread Synchronization</H2>
<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 because only some of the data set will have been updated.</P>
<P>Trying to keep threads working together properly is called <I>thread synchronization</I>.
Event objects, about which you just learned, are 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>
<H3><A NAME="Heading7"></A>Using Critical Sections</H3>
<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. 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 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 CCriticalSection class, like this:</P>
<P>
<PRE>CCriticalSection criticalSection;
</PRE>
<P>Then, when program code is about to access the data that you want to protect,
you call the critical-section object's Lock() member function, like this:</P>
<P>
<PRE>criticalSection.Lock();
</PRE>
<P>If another thread doesn't already own the critical section, Lock() gives the object
to the calling thread. That thread can then access the guarded data, after which
it calls the critical-section object's Unlock() member function:</P>
<P>
<PRE>criticalSection.Unlock();
</PRE>
<P>Unlock() 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>
<H4>Listing 27.6  COUNTARRAY.H--The CCountArray Class Header File</H4>
<PRE>#include "afxmt.h"
class CCountArray
{
private:
int array[10];
CCriticalSection criticalSection;
public:
CCountArray() {};
~CCountArray() {};
void SetArray(int value);
void GetArray(int dstArray[10]);
</PRE>
<PRE>};
</PRE>
<P>The header file starts by including the MFC header file, afxmt.h, which gives
the program access to the CCriticalSection class. Within the CCountArray class declaration,
the file declares a 10-element integer array, which is the data that the critical
section will guard, and declares the critical-section object, here called criticalSection.
The CCountArray class's public member functions include the usual constructor and
destructor, as well as functions for setting and reading the array. These latter
two member functions must deal with the critical-section object because these functions
access the array.</P>
<P>Listing 27.7 is the CCountArray 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 1 calls SetArray(), the first
thing SetArray() does is call criticalSection.Lock(), which gives the critical-section
object to thread 1. The complete for loop then executes, without any fear of being
interrupted by another thread. If thread 2 calls SetArray() or GetArray(), the call
to criticalSection.Lock() suspends thread 2 until thread 1 releases the critical-section
object, which it does when SetArray() finishes the for loop and executes the criticalSection.Unlock()
line. Then the system wakes up thread 2 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>
<H4>Listing 27.7  COUNTARRAY.CPP--The CCountArray Class Implementation
<BR>
File</H4>
<PRE>#include "stdafx.h"
#include "CountArray.h"
void CCountArray::SetArray(int value)
{
criticalSection.Lock();
for (int x=0; x<10; ++x)
array[x] = value;
criticalSection.Unlock();
}
void CCountArray::GetArray(int dstArray[10])
{
criticalSection.Lock();
for (int x=0; x<10; ++x)
dstArray[x] = array[x];
criticalSection.Unlock();
</PRE>
<PRE>}
</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 CCountArray class:</P>
<DL>
<DT></DT>
<DD><B>1.</B> Use the File, New command to add a new C++ header file called <B>CountArray.h</B>
to the project, as shown in Figure 27.9. Enter the code from Listing 27.6.
<P>
<DT></DT>
<DD><B>2.</B> Again choose File, New, and create a new C++ source file called <B>CountArray.cpp</B>
in this project. Enter the code from Listing 27.7.
<P>
<DT></DT>
<DD><B>3. </B>Switch to ThreadView.cpp and add the following line near the top of
the file, after the line #include "afxmt.h", which you placed there previously:
</DL>
<BLOCKQUOTE>
<PRE>#include "CountArray.h"</PRE>
</BLOCKQUOTE>
<PRE></PRE>
<DL>
<DT></DT>
<DD><B>4. </B>Add the following line near the top of the file, after the CEvent threadEnd
line you placed there previously:
</DL>
<BLOCKQUOTE>
<PRE>CCountArray countArray;</PRE>
</BLOCKQUOTE>
<PRE></PRE>
<DL>
<DT></DT>
<DD><B>5. </B>Delete the CEvent threadStart and CEvent threadEnd lines from the file.
<P>
</DL>
<P><A HREF="javascript:popUp('27uvc09.gif')"><B>FIG. 27.9</B></A><B> </B><I>Add CountArray.h
to the Thread project.</I></P>
<DL>
<DT><I></I></DT>
<DD><B>6. </B>Delete the lines ON_MESSAGE(WM_THREADENDED, OnThreadended), ON_COMMAND(ID_STOPTHREAD,
OnStopthread), and ON_WM_CREATE() from the message map.
<P>
<DT></DT>
<DD><B>7. </B>Replace the ThreadProc() function with the thread functions shown in
Listing 27.8.
<P>
</DL>
<H4>Listing 27.8  WriteThreadProc() and ReadThreadProc()</H4>
<PRE>UINT WriteThreadProc(LPVOID param)
{
for(int x=0; x<10; ++x)
{
countArray.SetArray(x);
::Sleep(1000);
}
return 0;
}
UINT ReadThreadProc(LPVOID param)
{
int array[10];
for (int x=0; x<20; ++x)
{
countArray.GetArray(array);
char str[50];
str[0] = 0;
for (int i=0; i<10; ++i)
{
int len = strlen(str);
wsprintf(&str[len], "%d ", array[i]);
}
::MessageBox((HWND)param, str, "Read Thread", MB_OK);
}
return 0;
</PRE>
<PRE>}
</PRE>
<DL>
<DT></DT>
<DD><B>8. </B>Replace all the code in the OnStartthread() function with the following
lines:
<P>
</DL>
<BLOCKQUOTE>
<PRE> HWND hWnd = GetSafeHwnd();
AfxBeginThread(WriteThreadProc, hWnd);
AfxBeginThread(ReadThreadProc, hWnd);</PRE>
</BLOCKQUOTE>
<PRE></PRE>
<DL>
<DT></DT>
<DD><B>9. </B>Delete the OnStopthread(), OnThreadended, and OnCreate() functions
from the file.
<P>
<DT></DT>
<DD><B>10. </B>Switch to the ThreadView.h file and delete the line const WM_THREADENDED
= WM_USER + 100 from the listing.
<P>
<DT></DT>
<DD><B>11. </B>Also, in ThreadView.h, delete the lines afx_msg LONG OnThreadended(WPARAM
wParam, LPARAM lParam), afx_msg void OnStopthread(), and afx_msg int OnCreate(LPCREATESTRUCT
lpCreateStruct) from the message map.
<P>
<DT></DT>
<DD><B>12. </B>Using the resource editor, remove the Stop Thread command from the
Thread menu.
<P>
</DL>
<P>Now build and run the new version of the Thread application. When you do, the
main window appears. Select the Thread, Start Thread command to get things hopping.
The first thing you'll see is a message box (see Figure 27.10) displaying the current
values in the guarded array. Each time you dismiss the message box, it reappears
with the array's new contents. The message box will reappear 20 times. The values
listed in the message box depend on how often you dismiss the message box. The first
thread is writing new values into the array once a second, even as you're viewing
the array's contents in the second thread.</P>
<P><A HREF="javascript:popUp('27uvc10.gif')"><B>FIG. 27.10</B></A><B> </B><I>This
message box displays the current contents of the guarded array.</I></P>
<P>The important thing to notice is that at no time does the second thread interrupt
when the first thread is changing the values in the array. You can tell that this
is true because the array always contains 10 identical values. If the first thread
were interrupted as it modified the array, the 10 values in the array would not be
identical, as shown in Figure 27.11.</P>
<P>If you examine the source code carefully, you'll see that the first thread, named
WriteThreadProc(), is calling the array class's SetArray() member function 10 times
within a for loop. Each time through the loop, SetArray() gives the thread the critical-section
object, changes the array contents to the passed number, and then takes the critical-section
object away again. Note the call to the Sleep() function, which suspends the thread
for the number of milliseconds given as the function's single argument.</P>
<P><A HREF="javascript:popUp('27uvc11.gif')"><B>FIG. 27.11</B></A><B> </B><I>Without
thread synchronization, you might see something like this in the message box.</I></P>
<P>The second thread, named ReadThreadProc(), is also trying to access the same critical-section
object to construct a display string of the values contained in the array. However,
if WriteThreadProc() is currently trying to fill the array with new values, ReadThreadProc()
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -