In-depth introduction to thread control in Win32 multi-threaded programming
Author: Song Baohua Source: Tianji development responsibility Editor: Fangzhou [] Win32 thread control mainly implements operations such as thread creation, termination, suspension, and recovery, these operations depend on a set of Apis provided by Win32 and the C Runtime library functions of a specific compiler. Win32 thread control mainly implements operations such as thread creation, termination, suspension, and recovery. These operations depend on a set of Apis provided by Win32 and the C Runtime library functions of a specific compiler.
1. Thread Functions
Before starting a thread, you must compile a global thread function for the thread. This thread function accepts a 32-bit lpvoid as the parameter and returns a uint. The thread function structure is:
Uint threadfunction (lpvoid pparam) { // Thread processing code Return0; } |
The code processing part of a thread usually includes an endless loop. This loop first waits for something to happen and then processes the related work:
While (1) { Waitforsingleobject (...,...); // Or waitformultipleobjects (...) // Do something } |
Generally, C ++ class member functions cannot be used as thread functions. This is because the compiler adds the this pointer to the member functions defined in the class. See the following program:
# Include "windows. H" # Include <process. h> Class exampletask { Public: Void taskmain (lpvoid PARAM ); Void starttask (); }; Void exampletask: taskmain (lpvoid PARAM) {}Void exampletask: starttask () { _ Beginthread (taskmain, 0, null ); } Int main (INT argc, char * argv []) { Exampletask realtimetask; Realtimetask. starttask (); Return 0; } |
The following error occurs during program Compilation:
Error c2664: '_ beginthread': cannot convert parameter 1 from 'void (void *) 'to 'void (_ cdecl *) (void *)' None of the functions with this name in scope match the target type |
Let's look at the following programs:
# Include "windows. H" # Include <process. h> Class exampletask { Public: Void taskmain (lpvoid PARAM ); };Void exampletask: taskmain (lpvoid PARAM) {} Int main (INT argc, char * argv []) { Exampletask realtimetask; _ Beginthread (exampletask: taskmain, 0, null ); Return 0; } |
An error occurs during program Compilation:
Error c2664: '_ beginthread': cannot convert parameter 1 from 'void (void *) 'to 'void (_ cdecl *) (void *)' None of the functions with this name in scope match the target type |
If you must use a class member function as a thread function, there are usually the following solutions:
(1) Declare the member function as static type and remove the this pointer;
We changed the above two programs:
# Include "windows. H" # Include <process. h> Class exampletask { Public: Void static taskmain (lpvoid PARAM ); Void starttask (); };Void exampletask: taskmain (lpvoid PARAM) {} Void exampletask: starttask () { _ Beginthread (taskmain, 0, null ); } Int main (INT argc, char * argv []) { Exampletask realtimetask; Realtimetask. starttask (); Return 0; } And # Include "windows. H" # Include <process. h> Class exampletask { Public: Void static taskmain (lpvoid PARAM ); }; Void exampletask: taskmain (lpvoid PARAM) {} Int main (INT argc, char * argv []) { _ Beginthread (exampletask: taskmain, 0, null ); Return 0; } |
All are compiled.
Although declaring a member function as static can solve the problem of using it as a thread function, it brings about a new problem, that is, the static member function can only access static members. One way to solve this problem is to pass this pointer as a parameter when calling a class static member function (thread function, in the thread function, force type conversion is used to convert this to a pointer to the class, and the pointer is used to access non-static members.
(2) do not define a class member function as a thread function, but define a thread function as a class member function. In this way, a thread function can have the same permissions as a class member function;
We changed the program:
# Include "windows. H" # Include <process. h> Class exampletask { Public: Friend void taskmain (lpvoid PARAM ); Void starttask (); };Void taskmain (lpvoid PARAM) { Exampletask * ptaskmain = (exampletask *) Param; // Use the ptaskmain pointer to reference } Void exampletask: starttask () { _ Beginthread (taskmain, 0, this ); } Int main (INT argc, char * argv []) { Exampletask realtimetask; Realtimetask. starttask (); Return 0; } |
(3) callback can be implemented for non-static member functions and non-static members are accessed. This method involves some advanced techniques and will not be detailed here. 2. Create a thread
The main thread of the process is automatically generated by the operating system. Win32 provides the createthread API to create user threads. The prototype of this API is:
Handle createthread ( Lpsecurity_attributes lpthreadattributes, // pointer to a security_attributes Structure Size_t dwstacksize, // initial size of the stack, in bytes. Lpthread_start_routine lpstartaddress, Lpvoid lpparameter, // pointer to a variable to be passed to the thread DWORD dwcreationflags, // flags that control the creation of the thread Lpdword lpthreadid // pointer to a variable that matches es the thread identifier ); |
If you use C/C ++ To Write multi-threaded applications, you must not use the createthread API provided by the operating system, instead, use _ beginthread (or _ beginthreadex) in the C/C ++ Runtime Library. Its function prototype is:
Uintptr_t _ beginthread ( Void (_ cdecl * start_address) (void *), // start address of routine that begins execution of new thread Unsigned stack_size, // stack size for new thread or 0. Void * Arglist // argument list to be passed to new thread or null ); Uintptr_t _ beginthreadex ( Void * Security, // pointer to a security_attributes Structure Unsigned stack_size, Unsigned (_ stdcall * start_address) (void *), Void * Arglist, Unsigned initflag, // initial state of New thread (0 for running or create_suincluded for suincluded ); Unsigned * thrdaddr ); |
The _ beginthread function is similar to the createthread function in Win32 API, but there are the following differences:
(1) through the _ beginthread function, we can use its parameter list Arglist to pass multiple parameters to the thread;
(2) The _ beginthread function initializes some C Runtime Library variables. If you need to use C Runtime Library in the thread.
3. Terminate the thread
There are four methods to terminate a thread:
(1) return of the thread function;
(2) The thread terminates itself by calling the exitthread function. Its prototype is:
Void exitthread (uint fuexitcode ); |
It sets the fuexitcode parameter to the exit code of the thread.
Note: If you use C/C ++ to write code, we should use the C/C ++ Runtime library function _ endthread (_ endthreadex) to terminate the thread and never use exitthread!
The _ endthread function is useful for condition termination within the thread. For example, if a thread dedicated to communication processing cannot obtain control over the communication port, it will exit.
(3) the thread of the same process or other processes calls the terminatethread function. Its prototype is:
Bool terminatethread (handle hthread, DWORD dwexitcode ); |
This function is used to end the thread specified by the hthread parameter and set dwexitcode to the exit code of the thread. When a thread no longer responds, we can use other threads to call this function to terminate this unresponsive thread.
(4) terminate a process containing threads.
It is best to terminate the thread in 1st ways, 2nd ~ None of the four methods should be used.
4. Thread suspension and recovery
When we create a thread, if we pass the create_suincluded flag to it, the thread will be suspended after being created. We should use resumethread to restore it:
DWORD resumethread (handle hthread ); |
If the resumethread function runs successfully, it returns the previous pause count of the thread; otherwise, it returns 0x ffffffff.
For threads that are not suspended, the programmer can call the suspendthread function to forcibly suspend them:
DWORD suspendthread (handle hthread ); |
A thread can be suspended for multiple times. The thread can pause the operation on its own, but cannot resume the operation on its own. If a thread is suspended for n times, the thread must be restored for n times before it can be executed. 5. Set thread priority
When a thread is created for the first time, its priority is equal to the priority of the process to which it belongs. In a single process, you can call the setthreadpriority function to change the relative priority of a thread. The priority of a thread is relative to the priority of the process to which it belongs.
Bool setthreadpriority (handle hthread, int npriority ); |
The hthread parameter points to the handle of the priority thread to be modified. The priority relationship between the thread and the process containing the priority thread is as follows:
Thread priority = Basic Process Priority + relative thread priority
The basic priority of a process class includes:
(1) Real-time: realtime_priority_class;
(2) Height: high _ priority_class;
(3) higher than normal: above_normal_priority_class;
(4) Normal: normal _ priority_class;
(5) lower than normal: Below _ normal _ priority_class;
(6) Idle: idle_priority_class.
We can intuitively see the priority of these six process classes from the Win32 Task Manager, such:
The relative priority of a thread includes:
(1) Idle: thread_priority_idle;
(2) Minimum thread: thread_priority_lowest;
(3) lower than the normal thread: thread_priority_below_normal;
(4) normal thread: thread_priority _ normal (default );
(5) higher than the normal thread: thread_priority_above_normal;
(6) Maximum thread: thread_priority_highest;
(7) critical time: thread_priotity_critical.
The ing between process priority and thread relative priority is given:
For example:
Handle hcurrentthread = getcurrentthread (); // Obtain the thread handle Setthreadpriority (hcurrentthread, thread_priority_lowest ); |
6. Sleep
Void sleep (DWORD dwmilliseconds ); |
This function can suspend the thread until dwmilliseconds milliseconds have elapsed. It tells the system that it does not want to be scheduled within a certain period of time.
7. Other important APIs
Obtain thread priority
When a thread is created, there will be a default priority, but sometimes the priority of a thread needs to be dynamically changed, and sometimes the priority of a thread needs to be obtained.
Int getthreadpriority (handle hthread ); |
If an error occurs in function execution, the thread_priority_error_return flag is returned. If the function is successfully executed, the priority flag is returned.
Obtain the thread exit code
Bool winapi getexitcodethread ( Handle hthread, Lpdword lpexitcode ); |
If the execution is successful, getexitcodethread returns true, and the exit code is pointed to the memory record by lpexitcode; otherwise, false is returned. We can learn the cause of the error through getlasterror. If the thread has not ended, the lpexitcode will bring back still_alive.
Obtain/set thread Context Bool winapi getthreadcontext ( Handle hthread, Lpcontext ); Bool winapi setthreadcontext ( Handle hthread, Const context * lpcontext ); |
Because getthreadcontext and setthreadcontext can operate the internal registers of the CPU, they are used in some advanced programming techniques. For example, the debugger can use getthreadcontext to suspend the debugging thread to obtain its context, set the trap flag in the flag register in the context, and use setthreadcontext to make the settings take effect for single-step debugging.
8. Instance
The following program uses createthread to create two threads. during sleep for a period of time, the main thread uses getexitcodethread to determine whether the two threads are running:
# Define win32_lean_and_mean # Include <stdio. h> # Include <stdlib. h> # Include <windows. h> # Include <conio. h>DWORD winapi threadfunc (lpvoid ); Int main () { Handle hthrd1; Handle hthrd2; DWORD exitcode1 = 0; DWORD exitcode2 = 0; DWORD threadid; Hthrd1 = createthread (null, 0, threadfunc, (lpvoid) 1, 0, & threadid ); If (hthrd1) Printf ("thread 1 launched/N "); Hthrd2 = createthread (null, 0, threadfunc, (lpvoid) 2, 0, & threadid ); If (hthrd2) Printf ("thread 2 launched/N "); // Keep waiting until both callto getexitcodethread succeed and // Neither of them returns still_active. For (;;) { Printf ("press any key to exit ../N "); Getch (); Getexitcodethread (hthrd1, & exitcode1 ); Getexitcodethread (hthrd2, & exitcode2 ); If (exitcode1 = still_active) Puts ("thread 1 is still running! "); If (exitcode2 = still_active) Puts ("thread 2 is still running! "); If (exitcode1! = Still_active & exitcode2! = Still_active) Break; } Closehandle (hthrd1 ); Closehandle (hthrd2 ); Printf ("thread 1 returned % d/N", exitcode1 ); Printf ("thread 2 returned % d/N", exitcode2 ); Return exit_success; } /* * Take the startup value, do some simple math on it, * And return the calculated value. */ DWORD winapi threadfunc (lpvoid N) { Sleep (DWORD) N * 1000*2 ); Return (DWORD) N * 10; } |
Through the following program, we can see the unpredictable program running sequence of multithreading and the difference between the createthread function of winapi and the _ beginthread function of C Runtime Library:
# Define win32_lean_and_mean # Include <stdio. h> # Include <stdlib. h> # Include <windows. h>DWORD winapi threadfunc (lpvoid ); Int main () { Handle hthrd; DWORD threadid; Int I; For (I = 0; I <5; I ++) { Hthrd = createthread (null, 0, threadfunc, (lpvoid) I, 0, & threadid ); If (hthrd) { Printf ("thread launched % d/N", I ); Closehandle (hthrd ); } } // Wait for the threads to complete. Sleep (2000 ); Return exit_success; } DWORD winapi threadfunc (lpvoid N) { Int I; For (I = 0; I <10; I ++) Printf ("% d/N", N ); Return 0; } |
The output of the operation is very random. Here we extract some of the results (almost every time it is different ):
If we use a standard C library function instead of a multi-threaded Runtime Library, the program may output results such as "3333444444". This problem can be avoided after the multi-threaded Runtime library is used.
The following program creates a secondthread in the main thread. In the secondthread, the counter is counted to 1000000 by auto-increment. The main thread waits until it ends:
# Include <win32.h> # Include <stdio. h> # Include <process. h>Unsigned counter; Unsigned _ stdcall secondthreadfunc (void * parguments) { Printf ("in second thread.../N "); While (counter< 1000000) Counter ++; _ Endthreadex (0 ); Return 0; } Int main () { Handle hthread; Unsigned threadid; Printf ("creating second thread.../N "); // Create the second thread. Hthread = (handle) _ beginthreadex (null, 0, & secondthreadfunc, null, 0, & threadid ); // Wait until second thread terminates Waitforsingleobject (hthread, infinite ); Printf ("counter shocould be 1000000; it is-> % d/N", counter ); // Destroy the thread object. Closehandle (hthread ); } |