Windows编程之线程
阅读原文时间:2023年07月10日阅读:1

本笔记整理自:《Windows核心编程(第五版)》

目录

何为线程

  • 线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。每一个进程至少包含一个线程。
  • 主线程是以main、wmain、WinMain或wWinMain作为入口的线程。
  • 线程即为程序作业的一道流程。程序(可以认为是一个进程)可以包含多个线程,这几个线程根据优先级与状态,有次序的利用CPU执行相应工作。

线程的开始和结束

  • CreateThread:Windows函数

    /*

    • include:Windows.h

    • 创建一个线程(操作系统级别的API)

    • @params:

    • psa : 指向 PSECURITY_ATTRIBUTES 结构体的指针。传入 NULL为默认

    • cbStackSize : 指定线程可以为其线程栈使用多少地址空间

    • pfnStartAddr: 线程的函数入口地址

    • pvParam : 函数入口的参数

    • dwCreateFlags : 创建线程的标志

    • pdwThreadID : 返回创建线程的ID
      */
      HANDLE CreateThread(
      PSECURITY_ATTRIBUTES psa,
      DWORD cbStackSize,
      PTHREAD_START_ROYTINE pfnStartAddr,
      PVOID pvParam,
      DWORD dwCreateFlags,
      PDWORD pdwThreadID
      );

      //samples:
      DWORD thread_id;
      HANDLE thread_handle;
      thread_handle = CreateThread(NULL, 2, ThreadFunc, NULL, CREATE_SUSPENDED, &thread_id);
      if (thread_handle)
      ResumeThread(thread_handle);

  • _beginthreadex:C-Rumtime Library函数 (建议使用)

    /*

    • include:process.h
    • 创建一个线程(操作系统级别的API)
    • @params:
    • security : 安全属性, 为NULL时表示默认安全性
    • stack_size : 线程的堆栈大小, 一般默认为0
    • start_address: 所要启动的线程函数
    • argilist : 线程函数的参数, 是一个void*类型, 传递多个参数时用结构体
    • initflag : 新线程的初始状态,0表示立即执行,CREATE_SUSPENDED表示创建之后挂起
    • threaddr : 用来接收线程ID
      */
      _beginthreadex
      void *security,
      unsigned stack_size,
      unsigned(_stdcall *start_address)(void *),
      void *argilist,
      unsigned initflag,
      unsigned *threaddr
      );
  • 方法(后面的三种都是“强杀”,不建议这么做)

    • 线程函数返回(最自然的一种方法,最好这么做)
    • 自身线程调用ExitThread
    • 同一个进程或另一个进程的线程调用TerminateThread
    • 包含线程的进程终止运行
  • 线程函数返回

    static unsigned _stdcall ThreadEnterProc(void* param)
    {
    while(!IsStop)
    {
    //TO-DO
    //…
    }
    return 0; //自然退出
    }

  • ExitThread:线程自身调用,用于自我关闭

    VOID ExitThread(DWORD dwExitCode); //哪个线程调用此函数就意味着要关闭哪一个线程(“自杀”)

  • TerminateThread:终止某线程。

注意此函数是异步的,因此就算返回了true,只能证明它响应了此操作,但不能保证线程已经停止运行

BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);

线程运行时的调度和线程优先级

  • 创建挂起的线程,可以让我们在恢复前修改线程的属性信息。
  • 线程可以自己调用函数挂起,但不能自己恢复。
  • 一个线程可以最多被挂起 MAXIMUM_SUSPEND_COUNT 次
  • 应用程序使用挂起操作时必须要小心,要明确知道正在进行什么操作。

挂起

/*
 *  @param:
 *  hThread:想要挂起的线程句柄
 *  @return
 *  返回线程的前一个挂起计数(注意是前一个)
 */
DWORD SuspendThread(HANDLE hThread);

恢复

/*
 *  @param:
 *  hThread:想要恢复的线程句柄
 *
 *  @return
 *  如果线程恢复成功,它将返回线程的前一个挂起计数(线程可以被多次挂起,因此这个返回值可以提示我们还要恢复几次才能获得 CPU 资源)
 *  否则,它将返回 0xFFFFFFFF
 */
DWORD ResumeThread(HANDLE hThread);

睡眠

  • 调用Sleep,将使用线程自愿放弃属于它的时间片中剩下的部分
  • Sleep的精确度是较低的(近似于所设定的毫秒),苏醒取决于当前系统其他线程的情况.
  • 传入参数:INFINITE是在告诉系统,永远不要分配 CPU 给这个系统
  • Sleep可以传递0,表示放弃时间片的剩余部分(以等待下一次轮询),CPU将让让渡给优先度相对或更高的线程。

基于上面对线程睡眠的解释,我们应该了解到,Sleep(milliseconds)并不意味着线程要暂停milliseconds。而是在milliseconds的时间内放弃占用CPU。milliseconds后此线程加入到线程待轮询队列,等待使用CPU。

/*
 *  将进程挂起dwMilliseconds的事件
 *
 *  @param
 *  dwMilliseconds : 睡眠时间
 */
DWORD Sleep(DWORD dwMilliseconds);
  • 线程切换相当于Sleep(0),但不同的点是线程切换忽略了优先级,它可以给优先级低的线程让出CPU。

  • 如果存在另一个可调度线程,那么系统会让此线程运行。

  • 调用函数时,系统查看是否存在正急需 CPU 时间的饥饿线程。如果没有,SwitchToThread立即返回。如果存在,SwitchToThread则调度该线程(忽略优先级)

    BOOL SwitchToThread();

Windows支持的线程相对优先级

相对线程优先级

符号常数

描述

time-critical

THREAD PRIORITY_ TIME CRITICAL

对于real-time优先级类,线程运行在31上;所有其他优先级运行在15

highest

THREAD_ PRIORITY_ HIGHEST

线程运行在高于normal之上一个级别

above normal

THREAD PRIORITY_ ABOVE_ NORMAL

线程运行在高于normal之上一个级别

normal

THREAD PRIORITY_ NORMAL

线程运行在进程normal级别上

below normal

THREAD PRIORITY_ BELOW_ NORMAL

线程运行在低于normal之下一一个级别

lowest

THREAD_ PRIORITY_ LOWEST

线程运行在低于normal之下两个级别

idle

THREAD_ PRIORITY_ IDLE

对于real-time优先级类,线程运行在16;所有其他优先级运行在1

设置线程优先级

BOOL SetThreadPriority(
  HANDLE hThread,
  int nPriority
);

是否启用动态提升优先级

/*
 *  启用or禁止动态提升优先级
 *
 *  @param
 *  hThread : 线程句柄
 *  bDisablePriorityBoost : 是否禁止。TRUE为禁用,FALSE为启用。
 */
BOOL SetThreadPriorityBoost(HANDLE hThread,BOOL bDisablePriorityBoost);

获取线程信息

HANDLE GetCurrentThread();


DWORD GetCurrentThreadId();


/*
 *  @params:
 *  hThread : 线程句柄
 *  pdwExitCode : 线程退出状态
 *
 *  Descriptions:
 *  若线程未终止,则 pdwExitCode == STILL_ACTIVE
 */
BOOL GetExitCodeThread(HANDLE hThread,PDWORD pdwExitCode);


//获取当前线程优先级
int GetThreadPriority(HANDLE hThread);


/*
 *  @Descriptions:获取当前想成上下文
 *
 *  @Notes:调用时请先挂起线程
 */
BOOL GetThreadContext(HANDLE hThread,PCONTEXT pContent);


/*
 *  @Descriptions:获取进程、线程的时间信息
 *
 *  @params:
 *  ftCreationTime :创建时间
 *  ftExitTime: 退出时间,运行时推出时间是没有定义的
 *  ftKernelTime : 线程执行内核模式下的操作系统代码所用时间
 *  ftUserTime : 线程执行用户代码所用时间的绝对值
 */
GetThreadTimes(GetCurrentThread(), &ftCreationTime, &ftExitTime, &ftKernelTime, &ftUserTime);

其他:句柄复制

/*
 *  注意:因为创建了一个句柄,使用计数+1,因此,当使用完成后需要调用 CloseHandle()
 */

BOOL DuplicateHandle
(
  HANDLE hSourceProcess,
  HANDLE hSource,
  HANDLE hTargetProcess,
  PHANDLE phTarget,
  DWORD dwDesiredAccess,
  BOOL bInheritHandle,
  DWORD dwOptions
);

//sample
HANDLE hCurrentThread;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), hCurrentThread, 0, FALSE, DUPLICATE_SAME_ACCESS);

//as Params And Use It,You must do:
CloseHandle(hCurrentThread);