VC multi threaded programming details

  • 2020-04-02 02:54:24
  • OfStack

This paper illustrates the concept and skills of VC multi - threaded programming, to share for your reference. Specific analysis is as follows:

One, multithreaded programming points

A thread is an execution path of a process that contains a separate stack and CPU register state, and each thread shares all process resources, including open files, signal identifiers, and dynamically allocated memory. All threads within a process use the same address space, and the execution of these threads is controlled by the system scheduler, which determines which threads are executable and when. Threads have priority levels, and the lower-priority threads must wait until the higher-priority threads finish executing. On a multiprocessor machine, the scheduler can place multiple threads on different processors to balance the processor's tasks and improve the system's performance.

Windows is a multitasking operating system that contains one or more threads within a process of Windows. Win32 API in 32-bit Windows environment provides the interface function needed for the development of multi-threaded application program, and the use of the standard C library provided in VC can also develop multi-threaded application program, the corresponding MFC class library encapsulates the multi-threaded programming class, the user can choose the corresponding tools according to the needs and characteristics of the application program. In order to make you can understand Windows multithreaded programming technology, this article will focus on Win32 API and MFC two ways how to program multithreaded.

Multi-threaded programming in Win32 and MFC library support under the principle is the same, the main thread of the process can be created whenever needed. When the thread is finished executing, the thread is terminated automatically. When the process ends, all threads terminate. All active threads share the process's resources, so you need to consider conflicts when multiple threads access the same resource when programming. When a thread is accessing a process object and another thread is changing the object, it may produce an incorrect result.

Two, Win32 API under the multithreaded programming

Win32 API is the interface between the kernel of the Windows operating system and the application program. It wraps the functions provided by the kernel into functions. The application program obtains the corresponding system functions by calling related functions. To provide multithreading capabilities to applications, the Win32 API function set provides a set of functions that work with multithreaded programs. Programming directly with the Win32 API has a number of advantages: win32-based applications have small execution code and high efficiency, but it requires programmers to write more code and manage all the resources the system provides to the program. Using Win32 API to directly write the program requires programmers to have a certain understanding of the Windows system kernel, will occupy programmers a lot of time to manage the system resources, so the programmer's work efficiency is reduced.

1. Create and terminate threads using the Win32 function

The Win32 function library provides functions to operate multiple threads, including creating threads, terminating threads, creating mutex and so on. The function to create a new thread in the main thread or other active thread of the application is as follows:

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);

Returns a handle to the thread on success, otherwise NULL. When a new thread is created, it starts execution. However, if you use the CREATE_SUSPENDED feature in dwCreationFlags, the thread will not execute immediately, but will suspend until the ResumeThread is called, during which time the thread's priority can be set by calling the following function:

BOOL SetThreadPriority(HANDLE hThread,int nPriority);

When the calling thread's function returns, the thread terminates automatically. If the thread needs to terminate during execution, the function can be called:

VOID ExitThread(DWORD dwExitCode);

If the thread is terminated outside of the thread, the following function can be called:

BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);

It should be noted, however, that this function may cause system instability, and the resources occupied by threads are not released. Therefore, it is generally not recommended to use this function.

If the thread to be terminated is the last thread in the process, the process should terminate after the thread is terminated.

2. Synchronization of threads

In a thread, if the thread is completely independent and has no conflicts with other threads on resource operations such as data access, it can be programmed in the usual single-threaded way. However, this is often not the case with multi-threading, where threads often have to access resources at the same time. Since access to Shared resources is inevitable, to solve this thread synchronization problem, the Win32 API provides a variety of synchronization control objects to help programmers resolve Shared resource access conflicts. Before we introduce these synchronization objects, let's introduce the wait function, which is used for access control of all control objects.

The Win32 API provides a set of wait functions that allow a thread to block its own execution. These functions generate a signal in one or more of their arguments or return after a specified wait time. When the waiting function does not return, the thread is in a waiting state and consumes very little CPU time. Using the wait function can not only guarantee the synchronization of threads, but also improve the running efficiency of the program. The most common wait function is:

DWORD WaitForSingleObject(HANDLE hHandle . DWORD dwMilliseconds);

The function WaitForMultipleObject can be used to simultaneously monitor multiple synchronized objects. The function is declared as:

DWORD WaitForMultipleObject(DWORD nCount,CONST HANDLE *lpHandles,BOOL bWaitAll,DWORD dwMilliseconds);

(1) mutex object

The state of a Mutex object is signaled when it is not owned by any thread, and no signaled when it is owned. Mutex objects are ideal for coordinating exclusive access to Shared resources between multiple threads. The object can be used as follows:

First, create the mutex object and get the handle:

HANDLE CreateMutex();

Then, a call to WaitForSingleObject is made before the thread has a possible conflicting area (that is, before the Shared resource is accessed), a handle is passed to the function, and a request is made to occupy the mutex:

dwWaitResult = WaitForSingleObject(hMutex,5000L);

End of Shared resource access, release the occupancy of the mutex object:

ReleaseMutex(hMutex);

A mutex object can only be occupied by one thread at a time. When a mutex object is occupied by one thread, if another thread wants to occupy it, it must wait until the previous thread is released.

(2) signal object

The signal object allows multiple threads to access a Shared resource at the same time, and specifies the maximum number of threads that can be accessed at the same time when the object is created. When a thread requests access successfully, the counter in the signal object is reduced by one, and the counter in the signal object is increased by one after calling the ReleaseSemaphore function. Where the counter value is greater than or equal to 0, but less than or equal to the maximum specified at creation time. If an application creates a signal object and sets the initial value of its counter to 0, it blocks other threads and protects the resource. When the initialization is complete, call the ReleaseSemaphore function to increase its counter to its maximum value for normal access. The object can be used as follows:

First, create the signal object:

HANDLE CreateSemaphore();

Or open a signal object:

HANDLE OpenSemaphore();

Then, call WaitForSingleObject before the thread accesses the Shared resource.

Upon completion of the Shared resource access, the occupation of the signal object shall be released:

ReleaseSemaphore();

(3) event object

An Event object (Event) is the simplest synchronization object, and it consists of two states: signaled and unsignaled. Event objects are best used when a thread needs to wait for an event to occur before accessing a resource. For example, the monitoring thread is activated only after the communication port buffer receives the data.

The event object is created using the CreateEvent function. This function specifies the class of the event object and the initial state of the event. If the event is reset manually, it always remains signaled until it is reset to a non-signaled event with the ResetEvent function. If it is an automatic reset event, its state automatically becomes non-signaled after a single waiting thread is released. Setevents can be used to set the event object to a signaled state. When an event is created, the object can be named so that threads in other processes can use the OpenEvent function to open the event object handle with the specified name.

(4) excluded objects

When executed asynchronously in an exclusion zone, it can only share resource processing between threads of the same process. While all of the methods described above are available at this point, the exclusion zone approach makes synchronization management more efficient.

Firstly, define a CRITICAL_SECTION structure of the exclusion object, and then call the following function to initialize the object before the process USES it:

VOID InitializeCriticalSection(LPCRITICAL_SECTION);

When a thread USES an exclusive section, the function is called: EnterCriticalSection or TryEnterCriticalSection.

When a LeaveCriticalSection is required to occupy and exit the excludable section, the function LeaveCriticalSection is called to release the occupation of the object in the excludable section for use by other threads.

Three, multithreaded programming based on MFC

MFC is the basic function library provided to programmers in the VC development and integration environment of Microsoft. It encapsulates Win32 API in the way of class library and provides it to developers in the way of class. Because of its fast, simple, powerful features by the majority of developers love. Therefore, it is recommended to use the MFC class library for application development.

In VC++ attached MFC class library, to provide support for multithreaded programming, the basic principle and based on the Win32 API design, but because MFC to do the synchronization object packaging, so it is more convenient to implement, avoid object handle management on the tedious work.

In MFC, there are two types of threads: worker threads and user interface threads. The worker thread is consistent with the thread described earlier, and the user interface thread is a thread that can receive input from the user and process events and messages.

1. Worker threads

The worker thread programming is relatively simple, the design idea is basically consistent with the previous said: a basic function represents a thread, after the creation and start the thread, the thread into the running state; If a thread USES a Shared resource, resource synchronization is required. This way you can call a function when creating and starting a thread:

CWinThread*AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority= THREAD_PRIORITY_NORMAL,
UINT nStackSize =0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

Parameter pfnThreadProc is the thread execution body function, function prototype: UINT ThreadFunction(LPVOID pParam).

The parameter pParam is passed to the execution function.

Parameter nPriority is the thread execution permission, with an optional value:

THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_HIGHEST, THREAD_PRIORITY_IDLE.

The parameter dwCreateFlags is a flag when a thread is created and can be evaluated as CREATE_SUSPENDED, indicating that the thread is suspended after creation, and the thread continues to run after the function ResumeThread is called, or "0" indicates that the thread is running after creation.

The return value is the CWinThread class object pointer, its member variable m_hThread is the thread handle, in the Win32 API mode on the thread operation of the function parameters are required to provide the thread handle, so when the thread is created can use all Win32 API functions on pWinThread- > M_Thread performs related operations.

Note: if a thread is created and started in a class object, the thread function should be defined as a global function outside the class.

2. User interface thread

An mfc-based application has an application object that is the object of a cwinapp-derived class that represents the main thread of the application process. When a thread finishes execution and exits, the process automatically ends because no other thread exists in the process. The class CWinApp is derived from CWinThread, which is the base class for user interface threads. When we write user-interface threads, we need to derive our own thread class from CWinThread, and the ClassWizard can help us with this.

Start with a new class derived from the ClassWizard, and set the base class to CwinThread. Note: the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros for the class are required because the objects of the class need to be created dynamically when the thread is created. The initialization and termination code can be placed in the InitInstance and ExitInstance functions of the class as needed. If a window needs to be created, this can be done in the InitInstance function. The thread is then created and started. There are two ways to create user interface threads. MFC provides two versions of the AfxBeginThread function, one of which is used to create user interface threads. The second approach takes two steps: first, the constructor of the thread class is called to create a thread object; Next, the CWinThread::CreateThread function is called to create the thread. After the thread is created and started, it remains valid for the duration of the execution of the thread function. If it is a thread object, the thread is terminated before the object is deleted. The CWinThread has done the thread termination for us.

3. Thread synchronization

Previously, we introduced several objects about thread synchronization provided by Win32 API. In the MFC class library, these objects are encapsulated in a class, they have a common base class CSyncObject, their correspondence is: Semaphore for CSemaphore, Mutex for CMutex, Event for CEvent, and CriticalSection for CCriticalSection. In addition, MFC also encapsulates two waiting functions, namely CSingleLock and CMultiLock. Since the four objects are similar in usage, this paper takes CMutex as an example to illustrate:

Create a CMutex object:

CMutex mutex(FALSE,NULL,NULL);

or

CMutex mutex;

When each thread wants to access the Shared resource, use the following code:

CSingleLock sl(&mutex);
sl.Lock();
if(sl.IsLocked())
//Operating on Shared resources...
sl.Unlock();

Four, the conclusion

If a user's application requires multiple tasks to be processed simultaneously, using multithreading is an ideal choice. Here, it is important to note that in multi-threaded programming you should be especially careful about resource sharing and multi-threaded debugging.

Hope that this article described the VC programming for you to help.


Related articles: