📄 ch27.htm
字号:
has to wait. The inverse is also true. That is, WriteThreadProc() can't access the
guarded data until it can regain ownership of the critical section from ReadThreadProc().</P>
<P>If you really want to prove that the critical-section object is working, remove
the criticalSection.Unlock() line from the end of the CCountArray class's SetArray()
member function. Then compile and run the program. This time when you start the threads,
no message box appears. Why? Because WriteThreadProc() takes the critical-section
object and never lets it go, which forces the system to suspend ReadThreadProc()
forever (or at least until you exit the program).</P>
<P>
<H3><A NAME="Heading8"></A>Using Mutexes</H3>
<P>Mutexes are a lot like critical sections but a little more complicated because
they enable safe sharing of resources, not only between threads in the same application
but also between threads of different applications. Although synchronizing threads
of different applications is beyond the scope of this chapter, you can get a little
experience with mutexes by using them in place of critical sections.</P>
<P>Listing 27.9 is the CCountArray2 class's header file. Except for the new classname
and the mutex object, this header file is identical to the original CountArray.h.
Listing 27.10 is the modified class's implementation file. As you can see, the member
functions look a lot different when they are using mutexes instead of critical sections,
even though both objects provide essentially the same type of services.</P>
<P>
<H4>Listing 27.9  CCOUNTARRAY2.H--The CCountArray2 Class Header File</H4>
<PRE>#include "afxmt.h"
class CCountArray2
{
private:
int array[10];
CMutex mutex;
public:
CCountArray2() {};
~CCountArray2() {};
void SetArray(int value);
void GetArray(int dstArray[10]);
</PRE>
<PRE>};
</PRE>
<H4>Listing 27.10  COUNTARRAY2.CPP--The CCountArray2 Class <BR>
Implementation File</H4>
<PRE>#include "stdafx.h"
#include "CountArray2.h"
void CCountArray2::SetArray(int value)
{
CSingleLock singleLock(&mutex);
singleLock.Lock();
for (int x=0; x<10; ++x)
array[x] = value;
}
void CCountArray2::GetArray(int dstArray[10])
{
CSingleLock singleLock(&mutex);
singleLock.Lock();
for (int x=0; x<10; ++x)
dstArray[x] = array[x];
</PRE>
<PRE>}
</PRE>
<P>To access a mutex object, you must create a CSingleLock or CMultiLock object,
which performs the actual access control. The CCountArray2 class uses CSingleLock
objects because this class is dealing with only a single mutex. When the code is
about to manipulate guarded resources (in this case, the array), you create a CSingleLock
object, like this:</P>
<P>
<PRE>CSingleLock singleLock(&mutex);
</PRE>
<P>The constructor's argument is a pointer to the thread-synchronization object that
you want to control. Then, to gain access to the mutex, you call the CSingleLock
object's Lock() member function:</P>
<P>
<PRE>singleLock.Lock();
</PRE>
<P>If the mutex is unowned, the calling thread becomes the owner. If another thread
already owns the mutex, the system suspends the calling thread until the mutex is
released, at which time the waiting thread is awakened and takes control of the mutex.</P>
<P>To release the mutex, you call the CSingleLock object's Unlock() member function.
However, if you create your CSingleLock object on the stack (rather than on the heap,
using the new operator) as shown in Listing 27.10, you don't have to call Unlock()
at all. When the function exits, the object goes out of scope, which causes its destructor
to execute. The destructor automatically unlocks the object for you.</P>
<P>To try out the new CCountArray2 class in the Thread application, add new CountArray2.h
and CountArray2.cpp files to the Thread project and then delete the original CountArray.h
and CountArray.cpp files. Finally, in ThreadView.cpp, change all references to CCountArray
to CCountArray2. Because all the thread synchronization is handled in the CCountArray2
class, no further changes are necessary to use mutexes instead of critical sections.</P>
<P>
<H3><A NAME="Heading9"></A>Using Semaphores</H3>
<P>Although semaphores are used like critical sections and mutexes in an MFC program,
they serve a slightly different function. Rather than enable only one thread to access
a resource at a time, semaphores enable multiple threads to access a resource, but
only to a point. That is, semaphores enable a maximum number of threads to access
a resource simultaneously.</P>
<P>When you create the semaphore, you tell it how many threads should be allowed
simultaneous access to the resource. Then, each time a thread grabs the resource,
the semaphore decrements its internal counter. When the counter reaches 0, no further
threads are allowed access to the guarded resource until another thread releases
the resource, which increments the semaphore's counter.</P>
<P>You create a semaphore by supplying the initial count and the maximum count, like
this:</P>
<P>
<PRE>CSemaphore Semaphore(2, 2);
</PRE>
<P>Because in this section you'll be using a semaphore to create a thread-safe class,
it's more convenient to declare a CSemaphore pointer as a data member of the class
and then create the CSemaphore object dynamically in the class's constructor, like
this:</P>
<P>
<PRE>semaphore = new CSemaphore(2, 2);
</PRE>
<P>You should do this because you have to initialize a data member in the constructor
rather than at the time you declare it. With the critical-section and mutex objects,
you didn't have to supply arguments to the class's constructors, so you were able
to create the object at the same time you declared it.</P>
<P>After you have created the semaphore object, it's ready to start counting resource
access. To implement the counting process, you first create a CSingleLock object
(or CMultiLock, if you're dealing with multiple thread-synchronization objects),
giving it a pointer to the semaphore you want to use, like this:</P>
<P>
<PRE>CSingleLock singleLock(semaphore);
</PRE>
<P>Then, to decrement the semaphore's count, you call the CSingleLock object's Lock()
member function:</P>
<P>
<PRE>singleLock.Lock();
</PRE>
<P>At this point, the semaphore object has decremented its internal counter. This
new count remains in effect until the semaphore object is released, which you can
do explicitly by calling the object's Unlock() member function:</P>
<P>
<PRE>singleLock.Unlock();
</PRE>
<P>Alternatively, if you've created the CSingleLock object locally on the stack,
you can just let the object go out of scope, which not only automatically deletes
the object but also releases the hold on the semaphore. In other words, both calling
Unlock() and deleting the CSingleLock object increment the semaphore's counter, enabling
a waiting thread to access the guarded resource.</P>
<P>Listing 27.11 is the header file for a class called CSomeResource. CSomeResource
is a mostly useless class whose only calling is to demonstrate the use of semaphores.
The class has a single data member, which is a pointer to a CSemaphore object. The
class also has a constructor and destructor, as well as a member function called
UseResource(), which is where the semaphore will be used.</P>
<P>
<H4>Listing 27.11  SOMERESOURCE.H</H4>
<PRE>#include "afxmt.h"
class CSomeResource
{
private:
CSemaphore* semaphore;
public:
CSomeResource();
~CSomeResource();
void UseResource();
</PRE>
<PRE>};
</PRE>
<P>Listing 27.12 shows the CSomeResource class's implementation file. You can see
that the CSemaphore object is constructed dynamically in the class's constructor
and deleted in the destructor. The UseResource() member function simulates accessing
a resource by attaining a count on the semaphore and then sleeping for five seconds,
after which the hold on the semaphore is released when the function exits and the
CSingleLock object goes out of scope.</P>
<P>
<H4>Listing 27.12  SOMERESOURCE.CPP</H4>
<PRE>#include "stdafx.h"
#include "SomeResource.h"
CSomeResource::CSomeResource()
{
semaphore = new CSemaphore(2, 2);
}
CSomeResource::~CSomeResource()
{
delete semaphore;
}
void CSomeResource::UseResource()
{
CSingleLock singleLock(semaphore);
singleLock.Lock();
Sleep(5000);
</PRE>
<PRE>}
</PRE>
<P>If you modify the Thread application to use the CSomeResource object, you can
watch semaphores at work. Follow these steps:</P>
<DL>
<DT></DT>
<DD><B>1. </B>Delete any CountArray files that are still in the project. (In FileView,
click the file once to select it; then press Del to delete the file from the project.)
<P>
<DT></DT>
<DD><B>2. </B>Create the new empty <B>SomeResource.h</B> and <B>SomeResource.cpp</B>
files in the project.
<P>
<DT></DT>
<DD><B>3. </B>Add the code from Listings 27.11 and 27.12 to these empty files.
<P>
<DT></DT>
<DD><B>4. </B>Load ThreadView.cpp and replace the line #include "CountArray2.h"
with the following:
<P>
</DL>
<BLOCKQUOTE>
<PRE>#include "SomeResource.h"</PRE>
</BLOCKQUOTE>
<PRE></PRE>
<DL>
<DT></DT>
<DD><B>5. </B>Replace the line CCountArray2 countArray with the following:
<P>
</DL>
<BLOCKQUOTE>
<PRE>CSomeResource someResource;</PRE>
</BLOCKQUOTE>
<PRE></PRE>
<DL>
<DT></DT>
<DD><B>6. </B>Replace the WriteThreadProc() and ReadThreadProc() functions with the
functions shown in Listing 27.13.
<P>
</DL>
<H4>Listing 27.13  ThreadProc1(), ThreadProc2(), and ThreadProc3()</H4>
<PRE>UINT ThreadProc1(LPVOID param)
{
someResource.UseResource();
::MessageBox((HWND)param,
"Thread 1 had access.", "Thread 1", MB_OK);
return 0;
}
UINT ThreadProc2(LPVOID param)
{
someResource.UseResource();
::MessageBox((HWND)param,
"Thread 2 had access.", "Thread 2", MB_OK);
return 0;
}
UINT ThreadProc3(LPVOID param)
{
someResource.UseResource();
::MessageBox((HWND)param,
"Thread 3 had access.", "Thread 3", MB_OK);
return 0;
</PRE>
<PRE>}
</PRE>
<DL>
<DT></DT>
<DD><B>7. </B>Replace the code in the OnStartthread() function with that shown in
Listing 27.14.
<P>
</DL>
<H4>Listing 27.14  LST27_14.TXT--New Code for the OnStartthread() Function</H4>
<PRE> HWND hWnd = GetSafeHwnd();
AfxBeginThread(ThreadProc1, hWnd);
AfxBeginThread(ThreadProc2, hWnd);
</PRE>
<PRE> AfxBeginThread(ThreadProc3, hWnd);
</PRE>
<P>Now compile and run the new version of the Thread application. When the main window
appears, select the Thread, Start Thread command. In about five seconds, two message
boxes will appear, informing you that thread 1 and thread 2 had access to the guarded
resource. About five seconds after that, a third message box will appear, telling
you that thread 3 also had access to the resource. Thread 3 took five seconds longer
because thread 1 and thread 2 grabbed control of the resource first. The semaphore
is set to allow only two simultaneous resource accesses, so thread 3 had to wait
for thread 1 or thread 2 to release its hold on the semaphore.</P>
<BLOCKQUOTE>
<P>
<HR>
<strong>NOTE:</strong> Although the sample programs in this chapter have demonstrated using
a single thread-synchronization object, you can have as many synchronization objects
as you need in a single program. You can even use critical sections, mutexes, and
semaphores all at once to protect different data sets and resources in different
ways. 
<HR>
</BLOCKQUOTE>
<P>For complex applications, threads offer the capability to maintain fast and efficient
data processing. You no longer have to wait for one part of the program to finish
its task before moving on to something else. For example, a spreadsheet application
could use one thread to update the calculations while the main thread continues accepting
entries from the user. Using threads, however, leads to some interesting problems,
not the least of which is the need to control access to shared resources. Writing
a threaded application requires thought and careful consideration of how the threads
will be used and what resources they'll access.</P>
<H1></H1>
<CENTER>
<P>
<HR>
<A HREF="../ch26/ch26.htm"><IMG SRC="../button/previous.gif" WIDTH="128" HEIGHT="28"
ALIGN="BOTTOM" ALT="Previous chapter" BORDER="0"></A><A HREF="../ch28/ch28.htm"><IMG
SRC="../button/next.gif" WIDTH="128" HEIGHT="28" ALIGN="BOTTOM" ALT="Next chapter"
BORDER="0"></A><A HREF="../index.htm"><IMG SRC="../button/contents.gif" WIDTH="128"
HEIGHT="28" ALIGN="BOTTOM" ALT="Contents" BORDER="0"></A> <BR>
</P>
<P>© <A HREF="../copy.htm">Copyright</A>, Macmillan Computer Publishing. All
rights reserved.
</CENTER>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -