Windows系统中每个窗口进程都有一个独立的消息队列。这个实现看起来很好:如果一个程序在花很长时间处理一条消息,那么当鼠标在该程序的窗口上时,鼠标指针会显示为沙漏,而当它在另一个程序的窗口上时,就变成正常的箭头了。简单的单击就能把另外的这个窗口调到前台。
然而,用户仍然不能在运行这个大任务的窗口工作,因为这个大任务阻止了程序接收其他消息。这不是我们想要的。一个程序应该总是能接收消息,而这常常就需要用到二级线程。
在Windows NT和Windows 98中,没有消息队列线程和非消息队列线程的区分。每个 线程在被生成时都有它自己的消息队列。在绝大多数情况下,你应该在主线程中通过消息过程处理输入,而把长时间运行的任务转给其他不带窗口的线程。我们很快就会看到,这种结构几乎总是最合理的。本节我们将讲述Windows多线程。
本节必须掌握的知识点:
多线程结构
线程的麻烦
与线程相关的函数
第147练:多线程画随机矩形
第148练:使用计时器模拟多线程
第149练:多线程解决方案
19.2.1 多线程结构
建议程序架构应该像这样:主线程创建程序所需要的所有窗口,包括这些窗口的所有窗口过程,并处理这些窗口的消息。任何其他的线程都应该是简单的后台运算。除了通过与主线程通信外,它们不和用户打交道。
主线程处理用户输入(和其他消息),在此过程中或许会生成其他的二级线程。这些额外的线程处理跟用户不相关的任务。
●处理用户界面的线程
这些线程创建窗口并设置消息循环来负责处理窗口消息,一个进程中并不需要太多这种线程,一般让主线程负责这个工作就可以了。同时,这种线程不该处理1/10秒以上的工作。
●工作线程
该类线程不处理窗口界面,当然也就不用处理消息了,它一般在后台运行,干一些繁重的、需要长时间运行的粗活,一般都要遵循1/10秒规则,但工作线程的要求相对比较宽松。
19.2.2 线程的麻烦
要正确地设计、编码和调试一个复杂的多线程应用程序,显然是一个Windows程序员会碰到的最困难的工作之一。因为一个抢占式多任务系统能够在任一点中断一个线程,把控制切换到另一个线程,因此这两个线程之间的任何不正常交互可能并不明显,而且可能只是偶尔才出现,看起来更像是随机的。
多线程程序中有两个常见的缺陷:
●竞争条件:不同的线程要访问同一资源,就不可避免会出现“竞争”,而问题解决的结果依赖于线程运行的时序,而不是程序的逻辑,这种情况称为竞争条件
●“死锁”:当两个程序己经中断彼此的运行,但是它们只有继续进行才能解除对方被中断的运行。

图19-1 死锁
如图19-1所示,T1线程请求使用R2资源,但此时R2资源正在被T2线程占用,因此T1线程只能挂起等待。当T2线程请求使用R1资源时,R1资源正在被T1线程所占用。因为此时T1线程处于挂起状态,无法及时释放R1资源,因此T2线程只能挂起等待。此时,演变为T1线程和T2线程相互等待对方释放资源R1和R2,这种情形称为“死锁”。这是所有程序员在处理多线程时都必须极力避免出现的情形。
19.2.3与线程相关的函数
■创建线程
CreateThread函数用于创建一个新的线程。它可以在一个进程内创建一个新的执行流,使得多个线程可以并发执行。
函数原型如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性,默认为NULL
SIZE_T dwStackSize, // 线程堆栈大小(以字节为单位),0为默认堆栈大小
LPTHREAD_START_ROUTINE lpStartAddress, // 线程入口点函数
LPVOID lpParameter, // 传递给线程函数的参数
DWORD dwCreationFlags, // 线程创建标志,用于控制线程的创建方式
LPDWORD lpThreadId // 接收线程标识符的变量,用于标识新线程
);
函数返回值为新线程的句柄(HANDLE 类型),如果创建线程失败,则返回 NULL。
使用 CreateThread 函数可以创建一个新的线程,并指定线程函数来执行特定的任务。可以通过设置线程的入口点函数和参数来定义线程的行为。创建后的线程可以与其他线程并发执行,实现多任务处理。
【注意】在使用 CreateThread 函数创建线程后,需要使用 CloseHandle 函数关闭线程句柄,以确保资源的正确释放。
以下是一个简单的示例代码,演示如何使用 CreateThread 函数创建一个新线程:
#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
printf("Thread function is executing.\n");
return 0;
}
int main() {
HANDLE hThread;
DWORD dwThreadId;
hThread = CreateThread(NULL, 0, ThreadFunction, NULL, 0, &dwThreadId);
if (hThread == NULL) {
printf("Failed to create thread.\n");
return 1;
}
// 等待线程结束
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
return 0;
}
以上代码中,ThreadFunction 是线程的入口点函数,在新线程中执行。CreateThread 函数创建一个新线程,并将 ThreadFunction 指定为线程的入口点。主线程使用 WaitForSingleObject 函数等待新线程的结束,最后使用 CloseHandle 函数关闭线程句柄。
C标准库函数:_beginthread
_beginthread是Windows平台上的一个C/C++运行时库函数,用于创建一个新线程并开始执行指定的线程函数。
以下是_beginthread函数的原型和用法:
uintptr_t _beginthread(
void (*start_address)(void*), //指向线程函数的指针
unsigned stack_size, // 线程的栈大小(字节为单位)0表示使用默认的栈大小
void* arglist //传递给线程函数的参数指针
);
_beginthread函数会创建一个新线程,并开始执行指定的线程函数。线程函数的参数可以通过arglist传递给线程函数。
以下是一个简单示例,演示如何使用_beginthread函数创建和启动一个新线程:
#include <process.h>
#include <stdio.h>
void ThreadFunction(void* arg) {
// 线程任务...
printf("Thread completed.\n");
}
int main() {
uintptr_t threadHandle;
threadHandle = _beginthread(ThreadFunction, 0, NULL);
if (threadHandle == -1) {
printf("Failed to create thread.\n");
return 1;
}
// 等待线程结束
// ...
return 0;
}
在上述示例中,我们使用_beginthread函数创建了一个新线程,并指定了一个线程函数ThreadFunction。在主函数中,我们可以执行一些操作来等待线程结束,例如使用WaitForSingleObject函数等待线程的句柄。
■线程函数
线程函数(Thread Function)是在线程中执行的代码块或函数,它定义了线程的具体行为和任务。线程函数是通过线程的入口点来调用的,当线程被创建并启动后,它会从线程函数的起始处开始执行。
DWORD WINAPI ThreadProc(LPVOID lpParameter);
C语言中线程函数的定义和使用参见创建线程函数CreateThread示例代码。
■终止线程
在Windows操作系统中,可以使用TerminateThread函数来终止一个线程。该函数会立即终止目标线程的执行,不会给线程执行任何清理的机会,因此使用该函数需要谨慎。
以下是TerminateThread函数的原型:
BOOL TerminateThread(
HANDLE hThread, //目标线程的句柄
DWORD dwExitCode //线程的退出代码,表示线程被终止的原因
);
函数返回值为 BOOL 类型,表示函数执行的成功与否。如果函数成功执行,返回值为非零值;如果函数执行失败,返回值为零。
【注意】终止线程可能导致一些不可预测的结果和资源泄漏。因此,建议在正常情况下使用线程间的协作机制来安全地结束线程,如使用共享变量、事件、条件变量等。
以下是一个示例代码,演示如何使用TerminateThread函数终止一个线程:
#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
while (1) {
// 线程任务...
}
return 0;
}
int main() {
HANDLE hThread;
DWORD dwThreadId;
hThread = CreateThread(NULL, 0, ThreadFunction, NULL, 0, &dwThreadId);
if (hThread == NULL) {
printf("Failed to create thread.\n");
return 1;
}
// 等待一段时间,让线程执行
Sleep(2000);
// 终止线程
if (TerminateThread(hThread, 0)) {
printf("Thread terminated.\n");
} else {
printf("Failed to terminate thread.\n");
}
CloseHandle(hThread);
return 0;
}
在上述示例中,我们创建了一个线程,并在线程函数中使用一个无限循环来模拟线程的执行。在主函数中,等待线程执行了一段时间后,调用TerminateThread函数来终止线程。然后,通过判断函数的返回值来确定是否成功终止线程。
安全地结束一个线程的执行是一个重要的任务,可以通过以下几种方式来实现:
●协作标志:使用一个共享的标志或变量,线程在执行过程中检查该标志或变量的状态,当标志或变量满足某个条件时,线程自行退出。这种方式需要线程函数周期性地检查标志或变量,以决定是否退出。
●通知事件:使用事件对象(如CreateEvent函数创建的事件)进行线程间的通信。主线程或其他线程可以通过设置事件来通知目标线程退出。线程在执行过程中等待事件的信号,当收到信号时,线程退出。
●取消标志:使用取消标志来通知线程退出。线程在执行过程中周期性地检查取消标志,当标志被设置时,线程自行退出。这种方式需要线程函数的设计支持,合适的时机检查取消标志。
●线程间消息传递:使用消息队列或消息机制,主线程或其他线程发送特定消息给目标线程,通知它退出。线程在执行过程中检查消息队列,当收到退出消息时,线程退出。
●清理函数:在线程函数中使用清理函数(cleanup function)来释放资源和执行必要的清理操作。主线程或其他线程可以通过设置标志来通知目标线程执行清理函数,并安全地终止线程。
需要注意的是,以上方法都需要在线程函数中进行适当的处理,确保在退出之前释放资源、关闭句柄等操作,以避免资源泄漏和不一致的状态。
C/C++语言中的线程退出函数:
pthread_exit():在POSIX标准的线程库中,用于退出当前线程,并返回一个退出码。
CreateThread API函数创建线程,ExitThread API函数销毁线程。
_beginthread:创建线程,_endthread来销毁线程
exit():在C标准库中,用于终止整个进程,包括所有线程。
return语句:在线程函数中使用return语句来退出线程。
●ExitThread函数
ExitThread函数会立即终止当前线程的执行,不会执行后续的代码。线程终止后,它的资源会被自动清理,包括栈、句柄等。
【注意】ExitThread函数只能用于退出当前线程,不能用于终止其他线程。如果要终止其他线程,可以使用TerminateThread函数,但请谨慎使用,因为它可能会导致资源泄漏和不一致的状态。
以下是一个简单示例,演示如何使用ExitThread函数退出线程:
#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
// 线程任务...
// 退出线程
DWORD exitCode = 123;
ExitThread(exitCode);
}
●_endthread函数
_endthread函数是一个在Windows平台上的C/C++运行时库函数,用于终止线程的执行。它与ExitThread函数类似,都是用于退出线程的函数,但在使用上有一些差异。
以下是_endthread函数的原型和用法:
void _endthread(void);
_endthread函数会终止当前线程的执行,并自动清理线程相关的资源,包括栈空间等。与ExitThread函数不同,_endthread函数不需要传递退出码,它会自动使用默认的退出码。
【注意】_endthread函数只能用于退出当前线程,不能用于终止其他线程。
以下是一个简单示例,演示如何使用_endthread函数退出线程:
#include <process.h>
#include <stdio.h>
void ThreadFunction(void* arg) {
// 线程任务...
// 退出线程
_endthread();
}
●当线程终止运行时
线程终止运行时,会发生下面这些事情:
1.线程拥有的所有用户对象句柄会被释放。在Windows中,大多数对象都是由包含了“创建这些对象的线程”的进程拥有的。但是,一个线程有两个User对象:窗口(window)和挂钩(hook)。一个线程终止运行时,系统会自动销毁由线程创建或安装的任何窗口,并卸载由线程创建或安装的任何挂钩。其他对象只有在拥有线程的进程终止时才被销毁。
2.线程的退出代码从STILL_ACTIVE变成传给ExitThread或TerminateThread的代码。
3.线程内核对象的状态变为signaled。
4.如果线程是进程中的最后一个活动线程,系统认为进程也终止了。
5.线程内核对象的使用计数递减1。
线程终止运行时,其关联的线程对象不会自动释放,除非对这个对象的所有未结束的引用都被关闭了。
一旦线程不再运行,系统中就没有别的线程可以处理该线程的句柄。但是,其他线程可以调用GetExitCodeThread来检查hThread所标识的那个线程是否已终止运行;如果已终止运行,就判断其退出代码是什么:
BOOL GetExitCodeThread( HANDLE hThread,PDWORD pdwExitCode);
退出代码的值通过pdwExitCode指向的DWORD来返回。如果在调用GetExitCodeThread时,线程尚未终止,函数就用STILL_ACTIVE标识符(被定义为0x103)来填充DWORD。如果函数调用成功,就返回TRUE。
■获取线程退出码
GetExitCodeThread函数用于获取指定线程的退出代码。它可以查询一个线程的退出状态,以确定线程是正常退出还是异常退出。
函数原型如下:
BOOL GetExitCodeThread(
HANDLE hThread, //要查询的线程句柄
LPDWORD lpExitCode //指向 DWORD 类型变量的指针,用于接收线程的退出代码
);
函数返回值为 BOOL 类型,表示函数执行的成功与否。如果函数成功执行,返回值为非零值;如果函数执行失败,返回值为零。
GetExitCodeThread函数,线程结束后的退出码可被其他线程用GetExitCodeThread检测到。
以下是一个简单的示例代码,演示如何使用GetExitCodeThread函数获取线程的退出代码:
#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
Sleep(2000); // 模拟线程执行一段时间
return 123; // 设置线程的退出代码为123
}
int main() {
HANDLE hThread;
DWORD dwThreadId;
hThread = CreateThread(NULL, 0, ThreadFunction, NULL, 0, &dwThreadId);
if (hThread == NULL) {
printf("Failed to create thread.\n");
return 1;
}
// 等待线程结束
WaitForSingleObject(hThread, INFINITE);
// 获取线程的退出代码
DWORD exitCode;
if (GetExitCodeThread(hThread, &exitCode)) {
printf("Thread exit code: %d\n", exitCode);
} else {
printf("Failed to get thread exit code.\n");
}
CloseHandle(hThread);
return 0;
}
在上述示例中,ThreadFunction 是线程的入口点函数,在新线程中执行。在函数的最后,设置了线程的退出代码为123。在主线程中,我们创建一个新的线程,等待线程结束后,使用GetExitCodeThread函数获取线程的退出代码,并将其打印出来。
■挂起线程函数
SuspendThread函数用于挂起指定线程的执行。以下是SuspendThread函数的原型:
DWORD SuspendThread(
HANDLE hThread //要挂起的线程的句柄
);
函数返回值为 DWORD 类型,表示函数执行的结果。如果函数执行成功,返回值为线程的先前挂起计数;如果函数执行失败,返回值为-1。
注意事项:
●Su

1164

被折叠的 条评论
为什么被折叠?



