Friday, March 28, 2008

Multithreading with C++ and MFC

Multithreading with C++ and MFC from MSDN

The Microsoft Foundation Class Library (MFC) provides support for multithreaded applications. This article describes what processes and threads are, and MFC's approach to multithreading.

A "process" is an executing instance of an application. For example, when you double-click the Notepad icon, you start a process that runs Notepad.

A "thread" is a path of execution within a process. When you start Notepad, the operating system creates a process and begins executing the primary thread of that process. When this thread terminates, so does the process. This primary thread is supplied to the operating system by the startup code in the form of a function address. Usually, it is the address of the main or WinMain function that is supplied.

You can create additional threads in your application if you wish. You may want to do this to handle background or maintenance tasks when you don't want the user to wait for them to complete. All threads in MFC applications are represented by CWinThread objects. In most situations, you don't even have to explicitly create these objects; instead call the framework helper function AfxBeginThread, which creates the CWinThread object for you.

MFC distinguishes two types of threads: user-interface threads and worker threads. User-interface threads are commonly used to handle user input and respond to events and messages generated by the user. Worker threads are commonly used to complete tasks, such as recalculation, that do not require user input. The Win32 API does not distinguish between types of threads; it just needs to know the thread's starting address so it can begin to execute the thread. MFC handles user-interface threads specially by supplying a message pump for events in the user interface. CWinApp is an example of a user-interface thread object, as it derives from CWinThread and handles events and messages generated by the user.

Special attention should be given to situations where more than one thread may require access to the same object. The article Multithreading: Programming Tips describes techniques you can use to get around problems that may arise in these situations. The article Multithreading: How to Use the Synchronization Classes describes how to use the classes that are available to synchronize access from multiple threads to a single object.

Writing and debugging multithreaded programming is inherently a complicated and tricky undertaking, as you must ensure that objects are not accessed by more than one thread at a time. The articles in the Multithreading group do not teach the basics of multithreaded programming, only how to use MFC in your multithreaded program. The multithreaded MFC samples included in Visual C++ illustrate a few multithreaded Adding Functionality and Win32 APIs not encompassed by MFC, but are only intended to be a starting point.


Multithreading: Programming Tips

Multithreaded applications require stricter care than single-threaded applications when accessing data. Because there are multiple, independent paths of execution in use simultaneously in multithreaded applications, either the algorithms, the data, or both must be aware that data could be used by more than one thread at a time. This article explains techniques for avoiding potential problems when programming multithreaded applications with the Microsoft Foundation Class Library (MFC).

Accessing Objects from Multiple Threads

For size and performance reasons, MFC objects are not thread-safe at the object level, only at the class level. This means that you can have two separate threads manipulating two different CString objects, but not two threads manipulating the same CString object. If you absolutely must have multiple threads manipulating the same object, protect such access with appropriate Win32 synchronization mechanisms, such as critical sections. For more information on critical sections and other related objects, see Synchronization in the Platform SDK.

The class library uses critical sections internally to protect global data structures, such as those used by the debug memory allocation.

Accessing MFC Objects from Non-MFC Threads

If you have a multithreaded application that creates a thread in a way other than using a CWinThread object, you cannot access other MFC objects from that thread. In other words, if you want to access any MFC object from a secondary thread, you must create that thread with one of the methods described in the Multithreading: Creating User-Interface Threads or Multithreading: Creating Worker Threads articles. These methods are the only ones that allow the class library to initialize the internal variables necessary to handle multithreaded applications.

Windows Handle Maps

As a general rule, a thread can access only MFC objects that it created. This is because temporary and permanent Windows handle maps are kept in thread local storage to ensure protection from simultaneous access from multiple threads. For example, a worker thread cannot perform a calculation and then call a document's UpdateAllViews member function to have the windows that contain views on the new data modified. This will have no effect at all, because the map from CWnd objects to HWNDs is local to the primary thread. This means that one thread may have a mapping from a Windows handle to a C++ object, but another thread may map that same handle to a different C++ object. Changes made in one thread would not be reflected in the other.

There are several ways around this problem. The first is to pass individual handles (such as an HWND) rather than C++ objects to the worker thread. The worker thread then adds these objects to its temporary map by calling the appropriate FromHandle member function. You could also add the object to the thread's permanent map by calling Attach, but this should be done only if you are guaranteed that the object will exist longer than the thread.

Another method is to create new user-defined messages corresponding to the different tasks your worker threads will be performing and post these messages to the application's main window using ::PostMessage. This method of communication is similar to two different applications conversing except that both threads are executing in the same address space.

Communicating Between Threads

MFC provides a number of classes that allow threads to synchronize access to objects to maintain thread safety. Usage of these classes is described in the articles Multithreading: How to Use the Synchronization Classes and Multithreading: When to Use the Synchronization Classes. More information on these objects can be found in Synchronization in the Platform SDK.


Multithreading: Creating User-Interface Threads

A user-interface thread is commonly used to handle user input and respond to user events independently of threads executing other portions of the application. The main application thread (provided in your CWinApp-derived class) is already created and started for you. This article describes the steps necessary to create additional user-interface threads.

The first thing you must do when creating a user-interface thread is derive a class from CWinThread. You must declare and implement this class, using the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros. This class must override some functions, and can override others. These functions and what they should do are presented in the following table.

Functions to Override When Creating a User-Interface Thread

Function name

Purpose

ExitInstance

Perform cleanup when thread terminates. Usually overridden.

InitInstance

Perform thread instance initialization. Must be overridden.

OnIdle

Perform thread-specific idle-time processing. Not usually overridden.

PreTranslateMessage

Filter messages before they are dispatched to TranslateMessage and DispatchMessage. Not usually overridden.

ProcessWndProcException

Intercept unhandled exceptions thrown by the thread's message and command handlers. Not usually overridden.

Run

Controlling function for the thread. Contains the message pump. Rarely overridden.

MFC provides two versions of AfxBeginThread through parameter overloading: one for user-interface threads and the other for worker threads. To start your user-interface thread, call AfxBeginThread, providing the following information:

  • The RUNTIME_CLASS of the class you derived from CWinThread.
  • (Optional) The desired priority level. The default is normal priority. For more information on the available priority levels, see SetThreadPriority in the Platform SDK.
  • (Optional) The desired stack size for the thread. The default is the same size stack as the creating thread.
  • (Optional) CREATE_SUSPENDED if you want the thread to be created in a suspended state. The default is 0, or start the thread normally.
  • (Optional) The desired security attributes. The default is the same access as the parent thread. For more information on the format of this security information, see SECURITY_ATTRIBUTES in the Platform SDK.

AfxBeginThread does most of the work for you. It creates a new object of your class, initializes it with the information you supply, and calls CWinThread::CreateThread to start executing the thread. Checks are made throughout the procedure to make sure all objects are deallocated properly should any part of the creation fail.


Multithreading: Creating Worker Threads

A worker thread is commonly used to handle background tasks that the user shouldn't have to wait for to continue using your application. Tasks such as recalculation and background printing are good examples of worker threads. This article details the steps necessary to create a worker thread. Topics include:

Creating a worker thread is a relatively simple task. Only two steps are required to get your thread running: implementing the controlling function and starting the thread. It is not necessary to derive a class from CWinThread. You can if you need a special version of CWinThread, but it is not required for most simple worker threads. You can use CWinThread without modification.

Starting the Thread

There are two overloaded versions of AfxBeginThread: one for user-interface threads and one for worker threads. To begin execution of your worker thread, call AfxBeginThread providing the following information:

  • The address of the controlling function.
  • The parameter to be passed to the controlling function.
  • (Optional) The desired priority of the thread. The default is normal priority. For more information on the available priority levels, see SetThreadPriority in the Platform SDK.
  • (Optional) The desired stack size for the thread. The default is the same size stack as the creating thread.
  • (Optional) CREATE_SUSPENDED if you want the thread to be created in a suspended state. The default is 0, or start the thread normally.
  • (Optional) The desired security attributes. The default is the same access as the parent thread. For more information on the format of this security information, see SECURITY_ATTRIBUTES in the Platform SDK.

AfxBeginThread creates and initializes a CWinThread object for you, starts it, and returns its address so you can refer to it later. Checks are made throughout the procedure to make sure all objects are deallocated properly should any part of the creation fail.

Implementing the Controlling Function

The controlling function defines the thread. When this function is entered, the thread starts, and when it exits, the thread terminates. This function should have the following prototype:

UINT MyControllingFunction( LPVOID pParam );

The parameter is a single 32-bit value. The value the function receives in this parameter is the value that was passed to the constructor when the thread object was created. The controlling function can interpret this value in any manner it chooses. It can be treated as a scalar value, or a pointer to a structure containing multiple parameters, or it can be ignored. If the parameter refers to a structure, the structure can be used not only to pass data from the caller to the thread, but also to pass data back from the thread to the caller. If you use such a structure to pass data back to the caller, the thread will need to notify the caller when the results are ready. For information on communicating from the worker thread to the caller, see the article Multithreading: Programming Tips.

When the function terminates, it should return a UINT value indicating the reason for termination. Typically, this exit code is 0 to indicate success with other values indicating different types of errors. This is purely implementation dependent. Some threads may maintain usage counts of objects, and return the current number of uses of that object. To se e how applications can retrieve this value, see the article Multithreading: Terminating Threads.

There are some restrictions on what you can do in a multithreaded program written with the Microsoft Foundation Class Library. For descriptions of these restrictions and other tips on using threads, see the article Multithreading: Programming Tips.

Controlling Function Example

This example shows how to define a controlling function and use it from another portion of the program.

UINT MyThreadProc( LPVOID pParam )

{

CMyObject* pObject = (CMyObject*)pParam;

if (pObject == NULL ||

!pObject->IsKindOf(RUNTIME_CLASS(CMyObject)))

return 1; // if pObject is not valid

// do something with 'pObject'

return 0; // thread completed successfully

}

// inside a different function in the program

.

.

.

pNewObject = new CMyObject;

AfxBeginThread(MyThreadProc, pNewObject);

.

.

.


Multithreading: How to Use the Synchronization Classes

Synchronizing resource access between threads is a common problem when writing multithreaded applications. Having two or more threads simultaneously access the same data can lead to undesirable and unpredictable results. For example, one thread could be updating the contents of a structure while another thread is reading the contents of the same structure. It is unknown what data the reading thread will receive: the old data, the newly written data, or possibly a mixture of both. MFC provides a number of synchronization and synchronization access classes to aid in solving this problem. This article explains the classes available and how to use them to create thread-safe classes in a typical multithreaded application.

A typical multithreaded application has a class that represents a resource to be shared among threads. A properly designed, fully thread-safe class does not require you to call any synchronization functions. Everything is handled internally to the class, allowing you to concentrate on how to best use the class, not about how it might get corrupted. The best technique for creating a fully thread-safe class is to merge the synchronization class into the resource class. Merging the synchronization classes into the shared class is a straightforward process.

As an example, take an application that maintains a linked list of accounts. This application allows up to three accounts to be examined in separate windows, but only one can be updated at any particular time. When an account is updated, the updated data is sent over the network to a data archive.

This example application uses all three types of synchronization classes. Because it allows up to three accounts to be examined at one time, it uses CSemaphore to limit access to three view objects. When an attempt to view a fourth account occurs, the application either waits until one of the first three windows closes or it fails. When an account is updated, the application uses CCriticalSection to ensure that only one account is updated at a time. After the update succeeds, it signals CEvent, which releases a thread waiting for the event to be signaled. This thread sends the new data to the data archive.

Designing a Thread-Safe Class

To make a class fully thread-safe, first add the appropriate synchronization class to the shared classes as a data member. In the previous account-management example, a CSemaphore data member would be added to the view class, a CCriticalSection data member would be added to the linked-list class, and a CEvent data member would be added to the data storage class.

Next, add synchronization calls to all member functions that modify the data in the class or access a controlled resource. In each function, you should create either a CSingleLock or CMultiLock object and call that object's Lock function. When the lock object goes out of scope and is destroyed, the object's destructor calls Unlock for you, releasing the resource. Of course, you can call Unlock directly if you wish.

Designing your thread-safe class in this fashion allows it to be used in a multithreaded application as easily as a non-thread-safe class, but with complete safety. Encapsulating the synchronization object and synchronization access object into the resource's class provides all the benefits of fully thread-safe programming without the drawback of maintaining synchronization code.

The following code example demonstrates this method by using a data member, m_CritSection (of type CCriticalSection), declared in the shared resource class and a CSingleLock object. The synchronization of the shared resource (derived from CWinThread) is attempted by creating a CSingleLock object using the address of the m_CritSection object. An attempt is made to lock the resource and, once obtained, work is done on the shared object. Once the work is finished, the resource is unlocked with a call to Unlock.

CSingleLock singleLock(&m_CritSection);

singleLock.Lock();

// resource locked

//.usage of shared resource...

singleLock.Unlock();

Note CCriticalSection, unlike other MFC synchronization classes, does not have the option of a timed lock request. The waiting period for a thread to become free is infinite.

The drawbacks to this approach are that the class will be slightly slower than the same class without the synchronization objects added. Also, if there is a chance that more than one thread may delete the object, the merged approach might not always work. In this situation, it is better to maintain separate synchronization objects.

For example code that uses the synchronization classes, see the MFC sample programs MTGDI and MUTEXES. These and other MFC sample programs can be found in Visual C++ Samples.

For information on determining which synchronization class to use in different situations, see the article Multithreading: When to Use the Synchronization Classes. For more information on synchronization, see Synchronization in the Platform SDK. For more information on multithreading support in MFC, see the article Multithreading with C++ and MFC.


Multithreading: Terminating Threads

Two normal situations cause a thread to terminate: The controlling function exits or the thread is not allowed to run to completion. If a word processor used a thread for background printing, the controlling function would terminate normally if printing completed successfully. Should the user wish to cancel the printing, however, the background printing thread would have to be terminated prematurely. This article explains both how to implement each situation and how to get the exit code of a thread after it terminates.

Normal Thread Termination

For a worker thread, normal thread termination is simple: Exit the controlling function and return a value that signifies the reason for termination. You can use either the AfxEndThread function or a return statement. Typically, 0 signifies successful completion, but that is up to you.

For a user-interface thread, the process is just as simple: from within the user-interface thread, call PostQuitMessage in the Platform SDK. The only parameter that PostQuitMessage takes is the exit code of the thread. As for worker threads, 0 typically signifies successful completion.

Premature Thread Termination

Terminating a thread prematurely is almost as simple: Call AfxEndThread from within the thread. Pass the desired exit code as the only parameter. This stops execution of the thread, deallocates the thread's stack, detaches all DLLs attached to the thread, and deletes the thread object from memory.

AfxEndThread must be called from within the thread to be terminated. If you want to terminate a thread from another thread, you must set up a communication method between the two threads.

Retrieving the Exit Code of a Thread

To get the exit code of either the worker or the user-interface thread, call the GetExitCodeThread function. For information about this function, see the Platform SDK. This function takes the handle to the thread (stored in the m_hThread data member of CWinThread objects) and the address of a DWORD.

If the thread is still active, GetExitCodeThread will place STILL_ACTIVE in the supplied DWORD address; otherwise, the exit code is placed in this address.

Retrieving the exit code of CWinThread objects takes an extra step. By default, when a CWinThread thread terminates, the thread object is deleted. This means you cannot access the m_hThread data member because the CWinThread object no longer exists. To avoid this situation, do one of the following two things:

  • Set the m_bAutoDelete data member to FALSE. This allows the CWinThread object to survive after the thread has been terminated. You can then access the m_hThread data member after the thread has been terminated. If you use this technique, however, you are responsible for destroying the CWinThread object as the framework will not automatically delete it for you. This is the preferred method.

-or-

  • Store the thread's handle separately. After the thread is created, copy its m_hThread data member (using ::DuplicateHandle) to another variable and access it through that variable. This way the object is deleted automatically upon termination and you can still find out why the thread terminated. Be careful that the thread does not terminate before you can duplicate the handle. The safest way to do this is to pass CREATE_SUSPENDED to AfxBeginThread, store the handle, and then resume the thread by calling ResumeThread.

Either method allows you to determine why a CWinThread object terminated.


Multithreading: When to Use the Synchronization Classes

The six multithreaded classes provided with MFC fall into two categories: synchronization objects (CSyncObject, CSemaphore, CMutex, CCriticalSection, and CEvent) and synchronization access objects (CMultiLock and CSingleLock).

Synchronization classes are used when access to a resource must be controlled to ensure integrity of the resource. Synchronization access classes are used to gain access to these controlled resources. This article describes when to use each class.

To determine which synchronization class you should use, ask the following series of questions:

  1. Does the application have to wait for something to happen before it can access the resource (for example, data must be received from a communications port before it can be written to a file)?

If yes, use CEvent.

  1. Can more than one thread within the same application access this resource at one time (for example, your application allows up to five windows with views on the same document)?

If yes, use CSemaphore.

  1. Can more than one application use this resource (for example, the resource is in a DLL)?

If yes, use CMutex.

If no, use CCriticalSection.

CSyncObject is never used directly. It is the base class for the other four synchronization classes.

Example 1: Using Three Synchronization Classes

As an example, take an application that maintains a linked list of accounts. This application allows up to three accounts to be examined in separate windows, but only one can be updated at any particular time. When an account is updated, the updated data is sent over the network to a data archive.

This example application uses all three types of synchronization classes. Because it allows up to three accounts to be examined at one time, it uses CSemaphore to limit access to three view objects. When an attempt to view a fourth account occurs, the application either waits until one of the first three windows closes or it fails. When an account is updated, the application uses CCriticalSection to ensure that only one account is updated at a time. After the update succeeds, it signals CEvent, which releases a thread waiting for the event to be signaled. This thread sends the new data to the data archive.

Example 2: Using Synchronization Access Classes

Choosing which synchronization access class to use is even simpler. If your application is concerned with accessing a single controlled resource only, use CSingleLock. If it needs access to any one of a number of controlled resources, use CMultiLock. In example 1, CSingleLock would have been used, as in each case only one resource was needed at any particular time.

For example code that uses the synchronization classes, see the MFC sample programs MTGDI and MUTEXES. These and other MFC sample programs can be found in Visual C++ Samples.

For information on how the synchronization classes are used, see the article Multithreading: How to Use the Synchronization Classes. For more information on synchronization, see Synchronization in the Platform SDK. For more information on multithreading support in MFC, see the article Multithreading with C++ and MFC.

No comments:

如何发掘出更多退休的钱?

如何发掘出更多退休的钱? http://bbs.wenxuecity.com/bbs/tzlc/1328415.html 按照常规的说法,退休的收入必须得有退休前的80%,或者是4% withdrawal rule,而且每年还得要加2-3%对付通胀,这是一个很大...