searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

浅谈 Windows 编程中的 Thread

2023-07-20 03:25:07
1
0

线程对于 Windows 编程人员来说,并不陌生,但是一直以来,我对它的了解也只是基本的使用层面。对于很多细节,也并不是很了解。这作为一个 Windows 客户端开发人员,可以说是非常尴尬了。所以,抽了一点时间,仔细梳理了一下线程相关的内容。顺便记录下来。

一些常识

  • 基本状态:就绪,执行,阻塞
  • 堆公有、栈私有
  • 创建和结束所需要的系统开销:小
  • 没有自己的地址空间

创建线程

在 Windows 下创建一个线程,很自然的会想到

CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,
    _In_ DWORD dwCreationFlags,
    _Out_opt_ LPDWORD lpThreadId
    );
 

这个方法可以说对 Windows 应用开发人员并不陌生。当使用这个方法的时候,在平时使用的时候,比较多关注的就是 lpStartAddresslpParameter 。这是线程函数的入口以及参数。创建一个新线程之后,将会从这里开始执行。

但是对于 C++ 来说,其实有另一个方法

_ACRTIMP uintptr_t __cdecl _beginthreadex(
    _In_opt_  void*                    _Security,
    _In_      unsigned                 _StackSize,
    _In_      _beginthreadex_proc_type _StartAddress,
    _In_opt_  void*                    _ArgList,
    _In_      unsigned                 _InitFlag,
    _Out_opt_ unsigned*                _ThrdAddr
    );
 

在这里,_StartAddress_ArgList 则跟上述那两个参数是类似的作用。
然而在这两个方法的选择中,《Windows 核心编程》早有公断。

根据作者的说法是选择 _beginthreadex 替代 CreateThread 。而原因则要从 _beginthreadex 的实现上说起。

_beginthreadex 在 Windows 下的实现也是调用了 CreateThread ,毕竟在 Windows 系统中,只认这一种创建线程的方式。但是在这之前,它还会做一些额外工作。创建一个线程数据块( tiddata ),然后将入口和参数都保存到数据块中,最后还要把数据块保存在 TLS 中。之后还要初始化一个 SEH 帧,用来处理运行时产生的错误。然后在线程结束之前,释放掉 tiddata 。那这样看,确实要比 CreateThread 多做一些事情。

话说回来,如果不做这些事情,当然就会有问题。比较直接的问题就是内存泄漏。原因是,如果使用 CreateThread 创建线程,当调用一些运行库函数的时候,会检查这个 tiddata 。如果发现没有,则会自己搞出一个,而这个在线程结束的时候,就不会被正确释放,就出现了内存泄漏。

类似 errno 这种运行库函数,需要反应正确的错误信息,如果不记录线程相关信息,则会在多线程的时候出现错误,所以一个 tiddata 是必要的,这也说明了为什么这个 tiddata 无论什么情况都会存在。

所以综上所述,在创建线程是,应该选择 _beginthreadex

关于更详细的 _beginthreadex 内容,参考 _beginthread, _beginthreadex 这篇文章是最好了

TLS

上边说的 TLS。可谓是线程中不可缺少的东西。因为线程之间是共享地址空间的,所以当有一些每个线程自己所需要的数据的时候,就不那么方便。而 TLS 就是用来解决这个问题。存储在 TLS 中的数据,对于每个线程之间,是互相隔离的。

结束线程

尽可能的让线程执行完自然结束。不到万不得已的时候,都不要使用 ExitThread 或者是 _endthreadex 。因为会使主调线程不正常返回,导致构造的 C++ 对象都不会析构;如果使用 ExitThread 还会造成 tiddata 不会被释放。

后记

关于多线程编程其实坑不算少,唯有对 Thread 多一些了解,才能写出更高质量的代码。

0条评论
0 / 1000
Harper
17文章数
0粉丝数
Harper
17 文章 | 0 粉丝
原创

浅谈 Windows 编程中的 Thread

2023-07-20 03:25:07
1
0

线程对于 Windows 编程人员来说,并不陌生,但是一直以来,我对它的了解也只是基本的使用层面。对于很多细节,也并不是很了解。这作为一个 Windows 客户端开发人员,可以说是非常尴尬了。所以,抽了一点时间,仔细梳理了一下线程相关的内容。顺便记录下来。

一些常识

  • 基本状态:就绪,执行,阻塞
  • 堆公有、栈私有
  • 创建和结束所需要的系统开销:小
  • 没有自己的地址空间

创建线程

在 Windows 下创建一个线程,很自然的会想到

CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,
    _In_ DWORD dwCreationFlags,
    _Out_opt_ LPDWORD lpThreadId
    );
 

这个方法可以说对 Windows 应用开发人员并不陌生。当使用这个方法的时候,在平时使用的时候,比较多关注的就是 lpStartAddresslpParameter 。这是线程函数的入口以及参数。创建一个新线程之后,将会从这里开始执行。

但是对于 C++ 来说,其实有另一个方法

_ACRTIMP uintptr_t __cdecl _beginthreadex(
    _In_opt_  void*                    _Security,
    _In_      unsigned                 _StackSize,
    _In_      _beginthreadex_proc_type _StartAddress,
    _In_opt_  void*                    _ArgList,
    _In_      unsigned                 _InitFlag,
    _Out_opt_ unsigned*                _ThrdAddr
    );
 

在这里,_StartAddress_ArgList 则跟上述那两个参数是类似的作用。
然而在这两个方法的选择中,《Windows 核心编程》早有公断。

根据作者的说法是选择 _beginthreadex 替代 CreateThread 。而原因则要从 _beginthreadex 的实现上说起。

_beginthreadex 在 Windows 下的实现也是调用了 CreateThread ,毕竟在 Windows 系统中,只认这一种创建线程的方式。但是在这之前,它还会做一些额外工作。创建一个线程数据块( tiddata ),然后将入口和参数都保存到数据块中,最后还要把数据块保存在 TLS 中。之后还要初始化一个 SEH 帧,用来处理运行时产生的错误。然后在线程结束之前,释放掉 tiddata 。那这样看,确实要比 CreateThread 多做一些事情。

话说回来,如果不做这些事情,当然就会有问题。比较直接的问题就是内存泄漏。原因是,如果使用 CreateThread 创建线程,当调用一些运行库函数的时候,会检查这个 tiddata 。如果发现没有,则会自己搞出一个,而这个在线程结束的时候,就不会被正确释放,就出现了内存泄漏。

类似 errno 这种运行库函数,需要反应正确的错误信息,如果不记录线程相关信息,则会在多线程的时候出现错误,所以一个 tiddata 是必要的,这也说明了为什么这个 tiddata 无论什么情况都会存在。

所以综上所述,在创建线程是,应该选择 _beginthreadex

关于更详细的 _beginthreadex 内容,参考 _beginthread, _beginthreadex 这篇文章是最好了

TLS

上边说的 TLS。可谓是线程中不可缺少的东西。因为线程之间是共享地址空间的,所以当有一些每个线程自己所需要的数据的时候,就不那么方便。而 TLS 就是用来解决这个问题。存储在 TLS 中的数据,对于每个线程之间,是互相隔离的。

结束线程

尽可能的让线程执行完自然结束。不到万不得已的时候,都不要使用 ExitThread 或者是 _endthreadex 。因为会使主调线程不正常返回,导致构造的 C++ 对象都不会析构;如果使用 ExitThread 还会造成 tiddata 不会被释放。

后记

关于多线程编程其实坑不算少,唯有对 Thread 多一些了解,才能写出更高质量的代码。

文章来自个人专栏
Windows 编程
9 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0