PoEdu培训 Windows班 第二十六课 Windows 线程(七) 线程的状态
文章类别: 培训笔记 0 评论

PoEdu培训 Windows班 第二十六课 Windows 线程(七) 线程的状态

文章类别: 培训笔记 0 评论

Windows 线程(七) 线程的状态

通过 Spy++ 来观察线程

我们都知道, 我们通常会使用 Spy++ 工具来查找窗口
但是 Spy++ 还可以观察进程和线程
如图:
Alt 进程
Alt 进程

我们随便找一个线程, 观察线程的属性:
Alt 进程
我们可以看到, 里面有线程状态, 目前为等待
这就说明我们当前线程的暂停计数不为0

还有一个是上下文开关, 这个是当前线程被加载之后运行的次数

线程的状态

线程的状态:

线程的启动状态

在线程启动后, 会有一个CONTEXT(线程上下文)
会有使用计数, 值为2
会有暂停计数, 值为1
当CreateThread完成后, dwCreationFlags参数不是CREATE_SUSPENDED, 暂停计数会减一
暂停计数为0时, 进入CPU调度, 当前线程为可执行状态

线程的运行状态

进入运行状态后, 线程开始执行我们的函数功能
在执行过程中, 线程会时不时的进行切换

线程运行状态中的线程切换是不会修改暂停计数

线程的挂起状态

可以通过调用SuspendThreadAPI来挂起线程
注意: 如果是64位的程序, 需要调用Wow64SuspendThread来挂起线程

挂起状态就是线程暂停
它会让我们线程的暂停计数加一

暂停计数不为0时, CPU将线程从调度池中取出
该线程不会参与任何的CPU调度
CPU也不会管该线程的任何信号状态

#include <Windows.h>
#include <process.h>
#include <tchar.h>

unsigned int __stdcall ThreadMain(void*)
{
    INT nNum = 0;
    while(TRUE)
    {
        _tprintf(TEXT("%d\n"), nNum++);
    }
    return 0;
}

INT main()
{
    HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadMain, NULL, 0, NULL);
    Sleep(100);
    SuspendThread(hThread); // 挂起线程, 暂停计数+1
    SuspendThread(hThread); // 在挂起一次, 暂停计数依然+1, 下面就需要两次ResumeThread才行
    Sleep(1000);
    ResumeThread(hThread);  // 恢复线程运行, 暂停计数-1
    WaitForSingleObject(hThread, INFINITE);
    return 0;
}

SuspendThread是非实时的, 它只是向操作系统发送请求
它会等到当线程的当前CPU时间片运行完成之后, 由操作系统来进行操作
SuspendThread的返回值是当前线程被挂起的次数, 就是线程的暂停计数-1

注意, 不推荐进程挂起操作, 除非能保证挂起线程没有问题.

举例:
在线程中, 需要进行new操作
当new的空间分配完成, 但是还未标记这块堆空间被使用时, 线程被挂起
那么, 操作系统不会认为这块堆内存被使用, 可能会被其他线程进行使用修改
那么当线程再次恢复运行时, 去读取这个堆内存上的数据就是错误的.

通过SuspendThread的挂起和CPU调度时的挂起不是一回事

线程的等待/休眠状态

通过SleepAPI来使线程进入等待/休眠状态
不会改变线程的暂停计数
它会通知CPU在指定的毫秒数内不要调度本线程
Sleep的参数中的毫秒数并不是绝对的, 只是无限接近

调用Sleep后, 会进行如下操作:

  1. 线程将放弃剩余的时间片
  2. 通知操作系统不要来调度我, 然后等待指定毫秒数

    • 当传递的毫秒数为INFINITE时, 线程会永远等待
    • 一直等到进程消亡
    • 当传递的毫秒数为0时, 直接放弃剩余时间片

饥饿线程

通过SwitchToThreadAPI可以切换到另一个线程
要切换的线程是不确定的
它会根据操作系统的CPU时间饥饿度算法来切换
当线程调用SwitchToThread后, 会将剩余的时间片给饥饿度最高的线程执行

未完待续...

如有错误,请提出指正!谢谢.

回复