《Windows API每日一练》19.2 Windows多线程

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值