MultiThread
问题背景
例如,一个网络程序,如果你不用多线程,即只使用窗口线程,那么程序在接收到封包之前是阻塞的,那么你的窗口线程就会一直等待接收封包的函数执行完毕,那么你的窗口就像死了一样卡住不动了,用了多线程就不会这样。
进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。
线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。
每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。
多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。
线程的创建:
CreateThread
This function creates a thread to execute within the address space of the calling process.
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpsa,
DWORD cbStack,
LPTHREAD_START_ROUTINE lpStartAddr,
LPVOID lpvThreadParam,
DWORD fdwCreate,
LPDWORD lpIDThread
);
Parameters
lpsa
[in] Ignored. Must be NULL.
cbStack
[in] Ignored unless the STACK_SIZE_PARAM_IS_A_RESERVATION flag is used; in that case the cbStack parameter specifies the virtual memory reserved for the new thread.
When ignored, the default stack size for a thread is determined by the linker setting /STACK.
lpStartAddr
[in] Long pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread; represents the starting address of the thread. For more information on the thread function, see ThreadProc.
Note It is invalid to set lpStartAddr to NULL. If this value is passed in the parameter, the function will return ERROR_INVALID_PARAMETER.
lpvThreadParam
[in] Long pointer to a single 32-bit parameter value passed to the thread.
fdwCreate
[in] Specifies flags that control the creation of the thread.
The following table shows the values for this parameter.
Value |
Description |
CREATE_SUSPENDED |
The thread is created in a suspended state and does not run until the ResumeThread function is called. The thread can be run immediately after creation if the flag is not specified. |
STACK_SIZE_PARAM _IS_A_RESERVATION |
The cbStack parameter specified the maximum stack size instead of being ignored. This is a Windows CE only flag for the CreateThread function and is used to reserve the stack size for the thread created. By default, a thread is created with 64 KB stack reserved. You can use this flag to increase the stack size for the new thread that was created. |
lpIDThread
[out] Long pointer to a 32-bit variable that receives the thread identifier.
If this parameter is NULL, the thread identifier is not returned.
Return Values
A handle to the new thread indicates success.
NULL indicates failure.
To get extended error information, call GetLastError.
Remarks
The number of threads a process can create is limited by the available virtual memory and depends on the default stack size.
If every thread has 1 MB of stack space, you can create a maximum of 32 threads.
If you reduce the default stack size, you can create more threads.
Your application will perform better if you create one thread per process, and build queues of requests for which the application maintains the context information. That is because a thread processes all requests in a queue before processing requests in the next queue.
The new thread handle is created with THREAD_ALL_ACCESS to the new thread. The handle can be used in any function that requires a thread object handle.
Thread execution begins at the function specified by the lpStartAddr parameter. If this function returns, the DWORD return value is used to terminate the thread in an implicit call to the ExitThread function. Use the GetExitCodeThread function to get the thread's return value.
If the start address is invalid when the thread runs, an exception occurs, and the thread terminates. Thread termination due to a invalid start address is handled as an error exit for the thread's process. This behavior is similar to the asynchronous nature of CreateProcess, where the process is created even if it refers to invalid or missing .dll files.
The thread is created with a thread priority of THREAD_PRIORITY_NORMAL.
Use the GetThreadPriority and SetThreadPriority functions to get and set the priority value of a thread.
When a thread terminates, the thread object attains a signaled state, satisfying threads that were waiting on the object.
The thread object remains in the system until the thread has terminated and all handles to it are closed through a call to CloseHandle.
The ExitThread function, CreateThread function, and a process that is starting (as the result of a call by CreateProcess) are serialized between each other within a process. Only one of these events can happen in an address space at a time.
The following list shows the restrictions during the process:
- During process startup and DLL initialization routines, threads can be created, but they do not begin execution until DLL initialization is done for the process.
- In a process, only one thread at a time can be in a DLL initialization or detach routine.
Example:
hThread=CreateThread( //返回值为句柄,HANDLE hThread;
NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadFunc, //函数指针
NULL,
0,
&ThreadID); //线程ID,DWORD ThreadID;
线程的挂起:
SuspendThread Function
Suspends the specified thread.
A 64-bit application can suspend a WOW64 thread using the Wow64SuspendThread function.
Syntax
DWORD WINAPI SuspendThread(
__in HANDLE hThread
);
Parameters
hThread [in]
A handle to the thread that is to be suspended.
The handle must have the THREAD_SUSPEND_RESUME access right. For more information, see Thread Security and Access Rights.
Return Value
If the function succeeds, the return value is the thread's previous suspend count; otherwise, it is (DWORD) -1. To get extended error information, use the GetLastError function.
Remarks
If the function succeeds, execution of the specified thread is suspended and the thread's suspend count is incremented. Suspending a thread causes the thread to stop executing user-mode (application) code.
This function is primarily designed for use by debuggers. It is not intended to be used for thread synchronization. Calling SuspendThread on a thread that owns a synchronization object, such as a mutex or critical section, can lead to a deadlock if the calling thread tries to obtain a synchronization object owned by a suspended thread. To avoid this situation, a thread within an application that is not a debugger should signal the other thread to suspend itself. The target thread must be designed to watch for this signal and respond appropriately.
Each thread has a suspend count (with a maximum value of MAXIMUM_SUSPEND_COUNT). If the suspend count is greater than zero, the thread is suspended; otherwise, the thread is not suspended and is eligible for execution. Calling SuspendThread causes the target thread's suspend count to be incremented. Attempting to increment past the maximum suspend count causes an error without incrementing the count.
The ResumeThread function decrements the suspend count of a suspended thread.
结束挂起、执行线程
ResumeThread Function
Decrements a thread's suspend count. When the suspend count is decremented to zero, the execution of the thread is resumed.
Syntax
DWORD WINAPI ResumeThread(
__in HANDLE hThread
);
Parameters
hThread [in]
A handle to the thread to be restarted.
This handle must have the THREAD_SUSPEND_RESUME access right. For more information, see Thread Security and Access Rights.
Return Value
If the function succeeds, the return value is the thread's previous suspend count.
If the function fails, the return value is (DWORD) -1. To get extended error information, call GetLastError.
Remarks
The ResumeThread function checks the suspend count of the subject thread. If the suspend count is zero, the thread is not currently suspended. Otherwise, the subject thread's suspend count is decremented. If the resulting value is zero, then the execution of the subject thread is resumed.
If the return value is zero, the specified thread was not suspended. If the return value is 1, the specified thread was suspended but was restarted. If the return value is greater than 1, the specified thread is still suspended.
Note that while reporting debug events, all threads within the reporting process are frozen. Debuggers are expected to use the SuspendThread and ResumeThread functions to limit the set of threads that can execute within a process. By suspending all threads in a process except for the one reporting a debug event, it is possible to "single step" a single thread. The other threads are not released by a continue operation if they are suspended.
终止线程
ExitThread Function
Ends the calling thread.
Syntax
VOID WINAPI ExitThread(
__in DWORD dwExitCode
);
Parameters
dwExitCode [in]
The exit code for the thread.
Return Value
This function does not return a value.
Remarks
ExitThread is the preferred method of exiting a thread in C code. However, in C++ code, the thread is exited before any destructors can be called or any other automatic cleanup can be performed. Therefore, in C++ code, you should return from your thread function.
When this function is called (either explicitly or by returning from a thread procedure), the current thread's stack is deallocated, all pending I/O initiated by the thread is canceled, and the thread terminates. The entry-point function of all attached dynamic-link libraries (DLLs) is invoked with a value indicating that the thread is detaching from the DLL.
If the thread is the last thread in the process when this function is called, the thread's process is also terminated.
The state of the thread object becomes signaled, releasing any other threads that had been waiting for the thread to terminate. The thread's termination status changes from STILL_ACTIVE to the value of the dwExitCode parameter.
Terminating a thread does not necessarily remove the thread object from the operating system. A thread object is deleted when the last handle to the thread is closed.
The ExitProcess, ExitThread, CreateThread, CreateRemoteThread functions, and a process that is starting (as the result of a CreateProcess call) are serialized between each other within a process. Only one of these events can happen in an address space at a time. This means the following restrictions hold:
- During process startup and DLL initialization routines, new threads can be created, but they do not begin execution until DLL initialization is done for the process.
- Only one thread in a process can be in a DLL initialization or detach routine at a time.
- ExitProcess does not return until no threads are in their DLL initialization or detach routines.
A thread in an executable that is linked to the static C run-time library (CRT) should use _beginthread and _endthread for thread management rather than CreateThread and ExitThread. Failure to do so results in small memory leaks when the thread calls ExitThread. Another work around is to link the executable to the CRT in a DLL instead of the static CRT. Note that this memory leak only occurs from a DLL if the DLL is linked to the static CRT and a thread calls the DisableThreadLibraryCalls function. Otherwise, it is safe to call CreateThread and ExitThread from a thread in a DLL that links to the static CRT.
Use the GetExitCodeThread function to retrieve a thread's exit code.
强制终止线程
TerminateThread Function
Terminates a thread.
Syntax
BOOL WINAPI TerminateThread(
__inout HANDLE hThread,
__in DWORD dwExitCode
);
Parameters
hThread [in, out]
A handle to the thread to be terminated.
The handle must have the THREAD_TERMINATE access right. For more information, see Thread Security and Access Rights.
dwExitCode [in]
The exit code for the thread. Use the GetExitCodeThread function to retrieve a thread's exit value.
Return Value
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Remarks
TerminateThread is used to cause a thread to exit. When this occurs, the target thread has no chance to execute any user-mode code. DLLs attached to the thread are not notified that the thread is terminating. The system frees the thread's initial stack.
Windows Server 2003 and Windows XP/2000: The target thread's initial stack is not freed, causing a resource leak.
TerminateThread is a dangerous function that should only be used in the most extreme cases. You should call TerminateThread only if you know exactly what the target thread is doing, and you control all of the code that the target thread could possibly be running at the time of the termination. For example, TerminateThread can result in the following problems:
- If the target thread owns a critical section, the critical section will not be released.
- If the target thread is allocating memory from the heap, the heap lock will not be released.
- If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread's process could be inconsistent.
- If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL.
A thread cannot protect itself against TerminateThread, other than by controlling access to its handles. The thread handle returned by the CreateThread and CreateProcess functions has THREAD_TERMINATE access, so any caller holding one of these handles can terminate your thread.
If the target thread is the last thread of a process when this function is called, the thread's process is also terminated.
The state of the thread object becomes signaled, releasing any other threads that had been waiting for the thread to terminate. The thread's termination status changes from STILL_ACTIVE to the value of the dwExitCode parameter.
Terminating a thread does not necessarily remove the thread object from the system. A thread object is deleted when the last thread handle is closed.
将一条消息放入到指定线程的消息队列
PostThreadMessage Function
Posts a message to the message queue of the specified thread. It returns without waiting for the thread to process the message.
Syntax
BOOL WINAPI PostThreadMessage(
__in DWORD idThread,
__in UINT Msg,
__in WPARAM wParam,
__in LPARAM lParam
);
Parameters
idThread [in]
Type: DWORD
The identifier of the thread to which the message is to be posted.
The function fails if the specified thread does not have a message queue. The system creates a thread's message queue when the thread makes its first call to one of the User or GDI functions. For more information, see the Remarks section.
Message posting is subject to UIPI. The thread of a process can post messages only to posted-message queues of threads in processes of lesser or equal integrity level.
This thread must have the SE_TCB_NAME privilege to post a message to a thread that belongs to a process with the same locally unique identifier (LUID) but is in a different desktop. Otherwise, the function fails and returns ERROR_INVALID_THREAD_ID.
This thread must either belong to the same desktop as the calling thread or to a process with the same LUID. Otherwise, the function fails and returns ERROR_INVALID_THREAD_ID.
Msg [in]
Type: UINT
The type of message to be posted.
wParam [in]
Type: WPARAM
Additional message-specific information.
lParam [in]
Type: LPARAM
Additional message-specific information.
Return Value
Type: BOOL
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError. GetLastError returns ERROR_INVALID_THREAD_ID if idThread is not a valid thread identifier, or if the thread specified by idThread does not have a message queue.
Remarks
When a message is blocked by UIPI the last error, retrieved with GetLastError, is set to 5 (access denied).
The thread to which the message is posted must have created a message queue, or else the call to PostThreadMessage fails. Use the following method to handle this situation.
· Create an event object, then create the thread.
· Use the WaitForSingleObject function to wait for the event to be set to the signaled state before calling PostThreadMessage.
· In the thread to which the message will be posted, call PeekMessage as shown here to force the system to create the message queue.
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE)
· Set the event, to indicate that the thread is ready to receive posted messages.
The thread to which the message is posted retrieves the message by calling the GetMessage or PeekMessage function. The hwnd member of the returned MSG structure is NULL.
Messages sent by PostThreadMessage are not associated with a window. As a general rule, messages that are not associated with a window cannot be dispatched by the DispatchMessage function. Therefore, if the recipient thread is in a modal loop (as used by MessageBox or DialogBox), the messages will be lost. To intercept thread messages while in a modal loop, use a thread-specific hook.
The system only does marshalling for system messages (those in the range 0 to (WM_USER-1)). To send other messages (those >= WM_USER) to another process, you must do custom marshalling.
There is a limit of 10,000 posted messages per message queue. This limit should be sufficiently large. If your application exceeds the limit, it should be redesigned to avoid consuming so many system resources. To adjust this limit, modify the following registry key.
HKEY_LOCAL_MACHINE
SOFTWARE
Microsoft
Windows NT
CurrentVersion
Windows
USERPostMessageLimit
The minimum acceptable value is 4000.
补充:
1、线程执行结束之后会自动退出并释放相应资源。
以下情况终止一个线程:
调用了ExitThread函数;
线程函数返回:主线程返回导致ExitProcess被调用,其他线程返回导致ExitThread被调用;
调用ExitProcess导致进程的所有线程终止;
调用TerminateThread终止一个线程;
调用TerminateProcess终止一个进程时,导致其所有线程的终止。
当用TerminateProcess或者TerminateThread终止进程或线程时,DLL的入口函数DllMain不会被执行(如果有DLL的话)。