• [C++ 网络协议] Windows平台下的线程


    目录

    1.内核对象( Windows)

    2. 创建线程( Windows)

    3. 线程内核对象的两种状态

    3.1 内核对象状态的查看


    1.内核对象( Windows)

    在介绍Windows的线程之前,先介绍下Windows的内核对象。

    内核对象的概念:

    如线程、进程、文件、信号量、互斥量等等,这些都是由操作系统所创建的资源,也统一由操作系统来管理,操作系统为了方便管理它们,就会在创建它们的同时,生成数据块(也可视为结构体变量),这个数据块以记录相关信息的方式来管理各种资源,被称为“内核对象”。

    内核对象的归属:

    线程、文件等资源的创建请求都在进程中执行,但不能认为此时创建的内核对象所有者就是进程。其实,可以通过内核对象的概念很容易得出,内核对象的所有者是内核,而内核就是操作系统。

    总结:

    内核对象就是为了管理线程、文件等资源而由操作系统创建的数据块,其创建者和所有者均为操作系统。

    2. 创建线程( Windows)

    方法一:

    1. #include
    2. HANDLE CreateThread(
    3. LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全相关信息,传NULL为默认设置
    4. SIZE_T dwStackSize, //要分配给线程的栈大小,传0为默认大小
    5. LPTHREAD_START_ROUTING lpStartAddress, //传递线程的main函数信息
    6. LPVOID lpParameter, //调用main函数传递的参数
    7. DWORD dwCreationFlags, //指定线程创建后的行为,传0表示线程进入可执行状态
    8. LPDWORD lpThreadId //保存线程ID的变量的地址
    9. );
    10. 成功返回线程句柄
    11. 失败返回NULL

    只需考虑lpStartAddress参数(线程main函数信息),以及lpParameter参数(调用main函数传递的参数)即可,因为其他的都传0或NULL,除了lpThreadId。

    缺点:通过这个方式创建出来的线程里,在使用C/C++标准函数时,会不稳定。

    方法二:(线程安全的标准C函数)

    1. #include
    2. uintptr_t _beginthreadex(
    3. void* security, //线程安全相关信息,传NULL为默认设置
    4. unsigned stack_size, //要分配给线程的栈大小,传0为默认大小
    5. unsigned (* start_address)(void* ), //传递线程的main函数信息
    6. void* arglist, //调用main函数传递的参数
    7. unsigned initflag, //指定线程创建后的行为,传0表示线程进入可执行状态
    8. unsigned* thrdaddr //保存线程ID的变量的地址
    9. );
    10. );
    11. 成功返回线程句柄
    12. 失败返回0

    _beginthread函数和_beginthreadex函数的区别:

    前者会为了防止访问内核对象,让创建线程时返回的句柄失效。后者不会。

    注意:方式二的线程的main函数,需要在函数名前加上WINAPI宏,其是Windows的固有关键字,用于指定参数传递方向,分配的栈返回方式等函数调用相关规定。如:

    1. unsigned WINAPI ThreadFunc(void* arg)
    2. {
    3. ......
    4. }
    5. int main()
    6. {
    7. ......
    8. HANDLE hTrread=(HANDLE)_beginthreadex(NULL,0,ThreadFunc,(void*)¶m,0,&threadId);
    9. }

    句柄、内核对象、线程ID的关系:

    句柄可以引用内核对象,所以可以通过句柄来区分内核对象,通过内核对象可以区分线程。所以线程句柄成为可以区分线程的工具。

    那么线程句柄可以区分线程,那线程ID有什么用?

    线程ID也是用来区分线程的,但是它们的区别是:句柄的整数值在不同进程中可能会出现重复,但线程ID在跨进程的范围内不会出现重复。

    3. 线程内核对象的两种状态

    一种是:signaled状态,表示线程已终止

    一种是:non_signaled状态,表示线程未终止

    操作系统会把这种状态信息保存到内核对象里,所有进程和线程的内核对象初始状态都是non_signaled,其通过1个boolean变量来表示,当为FALSE时,为non_signaled状态,当为TRUE时,为signaled状态。默认为FALSE,线程/进程结束,就会置为TRUE。这个状态不是一致的,内核对象类型不同,进入的状态的情况也不同。

    3.1 内核对象状态的查看

    单个内核对象状态的查看:

    1. #include
    2. DWORD WaitForSingleObject(
    3. HANDLE hHandle, //查看状态的内核对象句柄
    4. DWORD dwMilliseconds //以1/1000秒为单位指定超时时间,传递INFINITE会阻塞住,
    5. //直到内核对象变为signaled状态
    6. );
    7. 成功返回事件信息,事件信息:成功进入signaled状态返回WAIT_OBJECT_0,超时返回WAIT_TIMEOUT
    8. 失败返回WAIT_FAILED

    该函数放回时,内核对象变为signaled状态,之后,有时会把相应内核对象又改为non-signaled状态。这种函数返回后自动切换回non-signaled状态的内核对象称为“auto-reset模式”的内核对象。反之,不会自动切换的内核对象就称为“manual-reset模式”的内核对象

    多个内核对象状态的查看:

    1. #include
    2. DWORD WaitForMultipleObjects(
    3. DWORD nCount, //验证的内核对象数
    4. const HANDLE* lpHandles, //存有内核对象句柄的数组地址值
    5. BOOL bWaitAll, //TRUE,则所有内核对象都变为signaled时返回
    6. //FALSE,则只要有一个验证对象的状态变为signaled时就返回
    7. DWORD dwMilliseconds //以1/1000秒为单位指定超时时间,传递INFINITE会阻塞住,
    8. //直到内核对象变为signaled状态
    9. );
    10. 成功返回事件信息,事件信息:成功进入signaled状态返回WAIT_OBJECT_0,超时返回WAIT_TIMEOUT
    11. 失败返回WAIT_FAILED
  • 相关阅读:
    保健用品行业B2B电子商务系统:供采交易全链路数字化,助推企业管理精细化
    2020 Java工程师面试题汇总
    Kali Linux渗透测试技术介绍【文末送书】
    Baize_ServoDriver_esp32(ROS+Arduino驱动舵机机械臂,通过串口或WiFi话题通信)(数字孪生:虚拟和现实同步)
    Java集合容器面试题(2023最新版)
    python毕业设计作品基于django框架 电影院购票选座系统毕设成品(8)毕业设计论文模板
    python学习笔记(9)—— 虚拟环境和包
    OpenSSL 生成 RootCA (根证书)并自签署证书(支持 IP 地址)
    『现学现忘』Git分支 — 39、Git中分支与对象的关系
    Vue中巧用computed配合watch实现监听多个属性的变化
  • 原文地址:https://blog.csdn.net/A_ns_wer_/article/details/133269747