Windows线程生灭 (一)

来源:互联网 发布:vscode配置go 编辑:程序博客网 时间:2024/04/27 14:22

一、线程创建

Windows线程在创建时会首先创建一个线程内核对象,它是一个较小的数据结构,操作系统通过它来管理线程。新线程可以访问进程内核对象的所有句柄、进程中的所有内存及同一进程中其它线程的栈。

创建有以下几种方式,分别说明

  1. CreateThread(...) (操作系统提供的API,尽量不要使用)
  2. _beginthread(...)
  3. _beginthreadex(...)
  4. AfxBeginThread(...) (MFC提供的接口)

首先声明一个线程函数,原型为:

DWORD FunThread(LPVOID pParam);

1. CreateThread()

该函数为操作系统提供,原型如下:

复制代码
HANDLE WINAPI CreateThread(  _In_opt_   LPSECURITY_ATTRIBUTES lpThreadAttributes ,  _In_       SIZE_T dwStackSize ,  _In_       LPTHREAD_START_ROUTINE lpStartAddress,  _In_opt_   LPVOID lpParameter,  _In_       DWORD dwCreationFlags,  _Out_opt_  LPDWORD lpThreadId );
复制代码

说明:

HeaderLibraryDllWinBase.hKernel32.libKernel32.dll

 

 

参数:

  lpThreadAttributes:指向SECURITY_ATTRIBUTES结构体的指针,记录线程的安全描述。决定子进程能否继承到返回的句柄,如果为NULL,则采用默认安全级别(THREAD_PRIORITY_NORMAL),同时返回句柄不能继承
  dwStackSize:指定线程栈大小,当为0时,表示栈使用默认大小
  lpStartAddress:线程函数指针
  lpParameter:线程函数参数
  dwCreationFlags:为0:表示线程创建后立即运行;为CREATE_SUSPEND:创建后挂起,此时可修改线程属性,通过ResumeThread唤醒;
  lpThreadId:一个指向threadID的指针,若对线程ID关注,则传值,否则置NULL

返回值:

  创建线程的句柄;

  若创建失败,则返回NULL,可用GetLastError()捕获错误;

MFC中也提供了CreateThread函数,它是CWinThread类的一个方法,如下

BOOL CreateThread(   DWORD dwCreateFlags = 0,   UINT nStackSize = 0,   LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

参数含义与返回值含义一致,它的调用方式是:

CWinThread thread1;thread1.CreateThread();

需要说明的是dwCreateFlags传值为CREATE_SUSPEND时, 要通过CWinThread::ResumeThread来唤醒

2. _beginthread(), _beginthreadex()

原型:

复制代码
unsigned long _beginthread(         void( __cdecl *start_address )( void * ),         unsigned stack_size, void *arglist );unsigned long _beginthreadex(         void *security,         unsigned stack_size,         unsigned ( __stdcall *start_address )( void * ),         void *arglist,         unsigned initflag,         unsigned *thrdaddr );            
复制代码

说明:

HeaderLibraryprocess.hLIBCMT.lib  MSVCRT.lib 

 

 

参数与上面CreateThread含义相同,不在赘述;

二者比较:

1. _beginthread中线程函数调用为_cdecl,且无返回值; _beginthreadex为_stdcall,有返回值;

2. _beginthreadex中initflag相当于CreateThread中的dwCreationFlags,thrdaddr相当于lpThreadId

3.在实现上_beginthreadex控制了一个_tiddata的线程数据块,里面存放了线程函数地址、参数的很多属性,之后再间接调用CreateThread(...);

4._beginthread则参数较少;

3. AfxBeginThread()

MFC提供的接口提供了二种不同类型线程的生成,即工作者线程和用户界面线程;可以简单理解用户界面线程包含用户界面,它有自己的消息队列,工作者线程用于计算等;

复制代码
CWinThread* AfxBeginThread(   AFX_THREADPROC pfnThreadProc,             //线程函数指针,函数原型为UINT _cdecl fnThread(LPVOID pParam);   LPVOID pParam,                            //线程函数参数   int nPriority = THREAD_PRIORITY_NORMAL,   //优先级,SetThreadPriority   UINT nStackSize = 0,                      //栈大小,单位是bytes,为0时表示按默认大小   DWORD dwCreateFlags = 0,                  //CREATE_SUSPENDED:创建后挂起; 0:创建后立即运行      LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL  //指向SECURITY_ATTRIBUTES结构的指针,为Null时表示默认安全属性); //创建一个工作者线程CWinThread* AfxBeginThread(   CRuntimeClass* pThreadClass,                //指向界面类指针,继承自CWinThread   int nPriority = THREAD_PRIORITY_NORMAL,   UINT nStackSize = 0,   DWORD dwCreateFlags = 0,   LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ); //创建一个用户界面线程
复制代码

说明:

HeaderAfxwin.h

 


4. 比较

1. _beginthread与_beginthreadex

  1. 在实现上_beginthreadex控制了一个_tiddata的线程数据块,里面存放了线程函数地址、参数的很多属性,之后再间接调用CreateThread(...);
  2. _beginthread则参数较少,有局限性;

2. AfxBeginThread与mfc的CreateThread

  1. AfxBeginThread一步创建,之后线程立即运行
  2. CWinThread::CreateThread二步创建,但它保存了线程对象,可以在连续的线程创建与运行完成结束之间再使用(Use CreateThread if you want to reuse the thread object between successive creation and termination of thread executions

二、线程终结

终结线程的几个方法:

  1. 等待线程函数运行完成自动结束
  2. ExitThread(),用于结束线程自身
  3. TerminateThread(),所有线程都可以用该方法结束
  4. 父进程关闭,子线程随之关闭

只建议使用第一种方法结束线程,其它的方式都对应有缺陷

下面给出几个结束过程中发生事情:

1.资源有序释放(如操作系统分配资源,用到的C++类析构),返回线程退出代码,线程内核对象使用计数-1

2.操作系统相关资源释放;但象C++类并未析构,造成内存泄露;这里如果用_beginthreadex建立线程,而用ExitThread或者_endthread来释放线程,则线程放在堆上的线程数据块_tiddata也未释放,内存泄露;

3.该函数为异步函数,即通知操作系统终结线程后立即返回,而不管系统是否已经真的结束了线程。同时线程栈也不会释放

4.用ExitProgerss, TerminateProcess函数关闭进程后,进程会调用TerminateThread来关闭线程,效果如3,线程的栈没有释放,申请的对象资源也没释放。

1. ExitThread()

VOID ExitThread(  DWORD dwExitCode);

说明:

HeaderLibrarywinbase.hcoredll.lib

 

 

参数:

dwExitCode: 指定线程的退出代码。可以通过GetExitCodeThread来查看一个线程的退出代码

返回值:无

说明:在线程结束后,会将线程内核对象中的ExitCode由STILL_ACTIVE转变为传入退出代码;与CreateThread对应

2. TerminateThread()

BOOL TerminateThread(  HANDLE hThread,  DWORD dwExitCode);

说明:

HeaderLibrarywinbase.hcoredll.lib

 

 

参数:

hThread: 要结束的线程句柄
dwExitCode: 指定线程的退出代码。可以通过GetExitCodeThread来查看一个线程的退出代码

返回值:0表示失败,非0表示成功;

3. 判断线程是否结束

BOOL GetExitCodeThread(  HANDLE hThread,  LPDWORD lpExitCode);
复制代码
//判断bool IsThreadExit(HANDLE hThread){    bool bRet = false;    DWORD dwExitCode;    if(GetExitCodeThread(hThread, &dwExitCode))    {        if(dwExitCode != STILL_ACTIVE)            bRet = true;    }    else    {        //error        err = GetLastError();           throw err;    }    return bRet; }
复制代码

三、注意事项

1.在C++多线程编程中,尽量使用_beginthreadex及_endthreadex,而不是其它接口。

不使用_beginthread原因:

(1)_beginthread函数参数不够多,某些要求达不到,

不使用_endthread原因:

(1)_endthread函数也是无参的,即线程的退出代码会被硬编码为0;

(2)该函数在调用ExitThread前,会调用CloseHandle,并传入新线程的句柄。类似下面代码会有错误

DWORD dwExitCode;HANDLE hThread = _beginthreadex(...);GetExitCodeThread(hThread, &dwExitCode);CloseHandle(hThread);

不使用CreateThread函数原因:

(1)标准C/C++运行库最初并不是为多线程程序而设计的(标准的C运行时库出现在操作系统对线程支持之前),而CreateThread是操作系统接口,调用它时系统不知道是C/C++来调用的,因此为了保证C/C++程序正常运行,要创建一个数据结构与运行库的每个线程关联,_beginthreadex就实现了这样的功能。换言之,在C/C++中用CreateThread创建线程是极度不安全的。

不使用ExitThread函数原因:

(1)操作系统相关资源释放;但象C++类并未析构,造成内存泄露;这里如果用_beginthreadex建立线程,而用ExitThread或者_endthread来释放线程,则线程放在堆上的线程数据块_tiddata也未释放,内存泄露;

2.C/C++编程中使用CreateThread会发生什么

当线程调用一个需要线程数据块_tiddata的运行库函数时,系统会首先通过线程局部存储(TLS,见下节)来找到线程数据块,若为NULL,C/C++运行库会主调线程分配并初始化一个_tiddata块并与线程关联。但若使用C/C++运行库的signal函数,则整个进程都会终止(因结构化异常处理帧SEH未就绪,RtlUserThreadStart会直接调用ExitProcess来结束进程);此外,若不通过_endthreadex来结束线程,线程数据块_tiddata不会释放,造成内存泄露。

参考:

1. <<Windows核心编程(第五版)>>

2. 关于_BEGINTHREADEX、_BEGINTHREAD和CREATETHREAD

3. MFC 多线程及线程同步

4. MSDN

PS:此文为转载,感谢博主的无私贡献,受益匪浅,几近重新认识

 

原创粉丝点击