A thread is an execution path of a process. It contains an independent stack and CPU register status. Each thread shares all process resources, including open files, signal IDs, and dynamically allocated memory. All threads in a process use the same address space, and the execution of these threads is scheduled by the system. Program The scheduler determines which thread can be executed and when to execute the thread. A thread has a priority level. A thread with a lower priority must wait until the thread with a higher priority completes execution. On a multi-processor machine, the scheduler can put multiple threads on different processors for running, which can balance processor tasks and improve system running efficiency.
Windows is a multi-task operating system that contains one or more threads in a Windows process. The 32-bit windows Win32 API provides the interface functions required for multi-threaded application development. The standard C library provided in VC can also be used to develop multi-threaded applications, the corresponding MFC class library encapsulates multi-threaded programming classes. You can select the appropriate tool according to the needs and features of the application during development. To enable you to fully understand windows multi-threaded programming technology, this article will focus on how to compile multi-threaded programs using Win32 API and MFC.
Multi-threaded programming is consistent with the principle supported by the MFC class library in Win32 mode. The main thread of a process can create a new thread whenever necessary. After the thread is executed, the thread is automatically terminated. When the process ends, all threads are terminated. All active threads share the resources of the process. Therefore, you must consider the issue of conflicts when multiple threads access the same resource during programming. When a thread is accessing a process object and another thread wants to change the object, it may produce incorrect results. This conflict should be resolved during programming.
Multi-thread programming under Win32 API
Win32 API is the interface between the Windows operating system kernel and applications. It packs functions provided by the kernel, and applications obtain corresponding system functions by calling related functions. To provide multi-threaded functions to applications, Win32 API functions provide function sets for processing multi-threaded programs. Using Win32 APIs for program design has many advantages: Win32-based application executionCodeSmall, high running efficiency, but it requires programmers to write a lot of code, and need to manage all the resources provided to the program by the system. Writing programs using Win32 APIs requires programmers to have a certain understanding of the Windows system kernel, which takes a lot of time for programmers to manage system resources, thus reducing the programmer's work efficiency.
1. Use Win32 functions to create and terminate threads
The Win32 function library provides multi-threaded functions, including creating threads, terminating threads, and creating mutex. Functions used to create a new thread in the main thread of the application or other active threads are as follows:
Handle createthread (lpsecurity_attributes lpthreadattributes, DWORD dwstacksize, lpthread_start_routine lpstartaddress, lpvoid lpparameter, DWORD dwcreationflags, lpdword lpthreadid );
If creation is successful, the thread handle is returned. Otherwise, null is returned. After a new thread is created, the thread starts execution. However, if the create_suincluded feature is used in dwcreationflags, the thread will not be executed immediately, but will be suspended first. The thread will not be started until resumethread is called, in this process, you can call the following function to set the thread priority:
Bool setthreadpriority (handle hthread, int npriority );
When the function of the calling thread returns, the thread is automatically terminated. To terminate a thread during execution, you can call the function:
Void exitthread (DWORD dwexitcode );
If the thread is terminated outside the thread, the following function can be called:
Bool terminatethread (handle hthread, DWORD dwexitcode );
Note: This function may cause system instability and the resources occupied by threads are not released. Therefore, we recommend that you do not use this function.
If the thread to be terminated is the last thread in the process, the corresponding process should be terminated after the thread is terminated.
2. Thread Synchronization
In the thread body, if the thread is completely independent and there is no conflict with other threads in resource operations such as data access, you can program it in the usual single-threaded way. However, this is often not the case when processing multiple threads, and some resources are frequently accessed between threads. Conflicts are inevitable because of access to shared resources. To solve this thread synchronization problem, Win32 API provides a variety of synchronization control objects to help programmers solve shared resource access conflicts. Before introducing these synchronization objects, we should first introduce the wait function, because this function is required for access control of all control objects.
Win32 API provides a set of wait functions that can block the thread's own execution. These functions generate signals for one or more synchronization objects in their parameters, or return results after the specified wait time is exceeded. When the waiting function does not return, the thread is in the waiting state. At this time, the thread only consumes a small amount of CPU time. Using the wait function can ensure thread synchronization and improve program running efficiency. The most common wait functions are:
DWORD waitforsingleobject (handle hhandle, DWORD dwmilliseconds );
The waitformultipleobject function can be used to monitor multiple synchronization objects at the same time. The declaration of this function is:
DWORD waitformultipleobject (DWORD ncount, const handle * lphandles, bool bwaitall, DWORD dwmilliseconds );
(1) mutex object
The status of a mutex object has a signal when it is not owned by any thread, but no signal when it is owned. Mutex objects are suitable for coordinating the mutex access to shared resources by multiple threads. To use this object, follow these steps:
First, create a mutex object and obtain the handle:
Handle createmutex ();
Then, call waitforsingleobject before the region where the thread may conflict (before accessing shared resources) to pass the handle to the function, and the request occupies the mutex object:
Dwwaitresult = waitforsingleobject (hmutex, 5000l );
The shared resource access ends and the usage of the mutex object is released:
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, you must wait until the previous thread is released to succeed.
(2) signal object
The signal object allows simultaneous access to resources shared by multiple threads. When creating an object, specify the maximum number of threads that can be accessed simultaneously. After a thread successfully requests access, the counter in the signal object is reduced by one. After the releasesemaphore function is called, the counter in the signal object is incremented by one. The counter value is greater than or equal to 0, but less than or equal to the maximum value specified at creation. If an application sets the initial value of its counter to 0 when creating a signal object, it blocks other threads and protects resources. After the initialization is complete, call the releasesemaphore function to increase its counter to the maximum value, and access can be normally performed. To use this object, follow these steps:
First, create a signal object:
Handle createsemaphore ();
Or open a signal object:
Handle opensemaphore ();
Then, waitforsingleobject is called before the thread accesses shared resources.
After the shared resource access is complete, the occupation of the signal object should be released:
Releasesemaphore ();
(3) event object
Event is the simplest synchronization object, which includes two states: signal and no signal. Before a thread accesses a resource, it needs to wait for an event to occur. In this case, it is most appropriate to use the event object. For example, the monitoring thread is activated only when the communication port buffer receives data.
The event object is created using the createevent function. This function can specify the class of the event object and the initial state of the event. If it is a manual reset event, it always remains in a signal State until it is reset to a non-signal event using the resetevent function. If an event is automatically reset, its status will automatically become unresponsive after a single waiting thread is released. You can use setevent to set the event object to a signal state. When creating an event, you can name the object. In this way, threads in other processes can use the openevent function to open the event object handle with the specified name.
(4) exclusion area object
During asynchronous execution in the exclusion area, it can only share resource processing among threads of the same process. Although the methods described above can be used at this time, the use of the exclusion area method makes synchronization management more efficient.
When using the critical_section structure, first define a partition object. before using the process, call the following function to initialize the object:
Void initializecriticalsection (lpcritical_section );
When a thread uses a exclusion zone, call the function: entercriticalsection or tryentercriticalsection;
When the exclusion zone needs to be occupied or exited, call the leavecriticalsection function to release the occupation of objects in the exclusion zone for other threads.
Multi-Thread Programming Based on MFC
MFC is the basic function library provided to programmers in Microsoft's VC development and integration environment. It encapsulates Win32 APIs in the form of class libraries and provides them to developers. Developers love it because of its fast, simple, and powerful features. Therefore, we recommend that you use the MFC class library for application development.
The MFC class library attached to VC ++ provides support for multi-threaded programming. The basic principle is the same as the Win32 API-based design. However, because MFC encapsulates the synchronization object, therefore, the implementation is more convenient, avoiding the tedious work of object handle management.
In MFC, there are two types of threads: worker threads and user interface threads. The working thread is consistent with the preceding thread. the user interface thread is a thread that can receive user input, process events, and messages.
1. worker threads
The working thread programming is relatively simple, and the design idea is basically the same as that mentioned above: A Basic Function represents a thread. After a thread is created and started, the thread enters the running state. If the thread uses shared resources, you need to synchronize the resources. When a thread is created and started in this way, the function can be called:
Cwinthread * afxbeginthread (afx_threadproc pfnthreadproc, lpvoid pparam, int npriority = Priority, uint nstacksize = 0, DWORD dwcreateflags = 0, callback = NULL). The callback parameter is a thread execution function, the original function is: uint threadfunction (lpvoid pparam ).
The pparam parameter is the parameter passed to the execution function;
The npriority parameter is the thread execution permission. Optional values:
Thread_priority_normal, thread_priority_lowest, thread_priority_highest, and thread_priority_idle.
The dwcreateflags parameter is a flag during thread creation. The value create_suincluded indicates that the thread is suspended after creation. After the resumethread function is called, the thread continues to run, or the value "0" indicates that the thread is running after being created.
The returned value is a cwinthread Class Object Pointer. Its member variable m_hthread is the thread handle. in Win32 API mode, thread handles are required for function parameters for thread operations, therefore, after a thread is created, you can use all Win32 API functions to perform related operations on the pwinthread-> m_thread thread.
Note: When creating and starting a thread 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, which is the object of the cwinapp derived class. This object represents the main thread of the application process. When the thread finishes execution and exits, the process stops automatically because there are no other threads in the process. The cwinapp class is derived from cwinthread, which is the basic class of the user interface thread. When writing User Interface threads, We need to derive our own thread classes from cwinthread. classwizard can help us complete this work.
Use classwizard to derive a new class and set the base class to cwinthread. Note: The declare_dyncreate and implement_dyncreate macros of the class are required because the class objects must be dynamically created during thread creation. You can put the initialization and end code in the initinstance and exitinstance functions of the class as needed. To create a window, you can complete it in the initinstance function. Then create a thread and start the thread. You can use two methods to create a user interface thread. MFC provides two versions of the afxbeginthread function, one of which is used to create a user interface thread. The second method is divided into two steps: first, call the constructor of the thread class to create a thread object, and then call the cwinthread: createthread function to create the thread. After a thread is created and started, it remains valid during thread function execution. If it is a thread object, end the thread before the object is deleted. Cwinthread has completed the end of the thread for us.
3. Thread Synchronization
We have introduced several objects related to thread synchronization provided by Win32 API. These objects are encapsulated in the MFC class library. They have a common base class csyncobject, their mappings are: semaphore corresponds to csemaphore, mutex corresponds to cmutex, event corresponds to cevent, criticalsection corresponds to ccriticalsection. In addition, MFC also encapsulates two wait functions, csinglelock and cmultilock. The usage of the four objects is similar. Here we take cmutex as an example to describe:
Create a cmutex object:
Cmutex mutex (false, null, null );
Or cmutex mutex;
Use the following code to access Shared resources:
Csinglelock SL (& mutex );
SL. Lock ();
If (SL. islocked ())
// Perform operations on shared resources...
SL. Unlock ();
Conclusion
If your application requires multiple tasks to be processed at the same time, multithreading is an ideal choice. Here, we should note that we should be especially careful when dealing with resource sharing and multi-thread debugging issues.