在微软的 Programming Techniques 说明文件中有一句看似悲惨的警告:
警告:如果你在一个与 LIBCMT.LIB 链接的程序中调用 C runtime 函数, 你的线程就必须以 _beginthread() 启动之。不要使用 Win32 的ExitThread() 和 CreateThread()。
首先看一下_beginthreadex:
1、如果你写一个多线程程序,而且没有使用MFC,那么你应该总是和多线程版本的C Run time library进行链接。并且应该总是以_beginthreadex和_endthreadex取代createthread和exitthread。_beginthread参数与createthread一致并且对C Runtime library进行了必要的初始化。
2、关于C Runtime library的多线程版本:之前的版本不能被多线程的应用程序所使用。由于它使用了很多全局变量以及静态变量,所以在多线程应用程序中很容易产生同步错误。因为这个原因,所以产生了C Runtime library的多线程版本。
3、你可以将_beginthreadex理解为createthread的外包函数,它记录了一些和启动线程相关的信息。
4、unsigned long _beginthreadex(
void *security, //安全属性
unsigned stack_size, //堆栈大小
unsigned (__stdcall *start_address)(void *), //回调函数
void *arglist,unsigned initflag, //创建标志
unsigned* thrdaddr //线程ID
);
一个简单的例子:
#include <iostream>
#include <cstdlib>
using namespace std;
#include <Windows.h>
#include <process.h>
unsigned _stdcall ThreadProc(void* param);
int main()
{
}
unsigned _stdcall ThreadProc(void* param)
{
}
如果一个多线程程序在worker线程中不会调用c runtime library多线程版本中函数的话,应该能够使用单线程版本的c runtime library并且可以通过createthread和exitthread来创建和结束线程。然而C程序不调用任何多线程版本的c runtime函数通常是不可能的,所以记住这一点:如果要产生一个多线程的C程序(不使用MFC),那么请一定使用_beginthreadex和_endthreadex,而不要使用createthread和_beginthread。
关于_beginthread:它被认为是一个头脑简单的函数,它并没有获得和createthread函数完全一样的参数,所以有些事情它办不到,比如说线程的挂起状态。另外重要的一点是,它所产生出来的线程所做的第一件事情就是关闭掉handle,所以由它所返回的handle可能可用,也可能不可用。
<<Windows核心编程>>中有很详细地介绍。我再说一些吧
_beginthreadex是微软的C/C++运行时库函数,CreateThread是操作系统的函数。
_beginthreadex通过调用CreateThread来实现的,但比CreateThread多做了许多工作。
注意:若要创建一个新线程,绝对不要使用CreateThread,而应使用_beginthreadex.
Why?考虑标准C运行时库的一些变量和函数,如errno,这是一个全局变量。全局变量用于
多线程会出什么事,你一定知道的了。故必须存在一种机制,使得每个线程能够引用它自己的
errno变量,又不触及另一线程的errno变量._beginthreadex就为每个线程分配自己的
tiddata内存结构。该结构保存了许多像errno这样的变量和函数的值、地址(自己看去吧)。
通过线程局部存储将tiddata与线程联系起来。具体实现在Threadex.c中有。
结束线程使用函数_endthreadex函数,释放掉线程的tiddata数据块。
1 HANDLE WINAPI CreateThread( 2 __in LPSECURITY_ATTRIBUTES lpThreadAttributes, 3 __in SIZE_T dwStackSize, 4 __in LPTHREAD_START_ROUTINE lpStartAddress, 5 __in LPVOID lpParameter, 6 __in DWORD dwCreationFlags, 7 __out LPDWORD lpThreadId 8 );
1 uintptr_t _beginthreadex( 2 void *security, 3 unsigned stack_size, 4 unsigned ( *start_address )( void * ), 5 void *arglist, 6 unsigned initflag, 7 unsigned *thrdaddr 8 );
两个函数都是用于创建线程,第一个是Windows API函数,在WinBase.h头文件中,第二个不是API函数,在process.h头文件中
参数说明:
1.线程安全性:表示是否可以被子进程所继承
2.初始堆栈大小:如果为0或者小于默认值,则使用和调用线程同样大小的空间
3.线程其实地址:一个函数指针,指向线程函数
4.参数:传递给线程函数的参数
5.创建选项:如果为CREATE_SUSPENDED表示创建后挂起,如果为0表示创建后立即执行
6.线程ID
两个函数的区别:
malloc、fopen、ctime等函数需要专门的线程局部存储数据块,这个数据块在创建线程时创建。如果用CreateThread,则不会创建,这样,函数能够正常使用,但是会自动创建数据块,但是函数并不会释放创建的数据库,所以并不会将其删除,就导致内存泄露!!!
而_beginthreadex(内部也调用CreateThread)和_beginthreadex(会自动调用CloseHandle关闭句柄)对这个内存块做了处理。
代码演示:
1 #include <iostream> 2 #include <Windows.h> 3 #include <process.h> 4 using namespace std; 5 6 7 DWORD WINAPI CreateFun(LPVOID lParam) 8 { 9 cout << "CreateThread" << endl; 10 return 0;//0表示成功 11 12 } 13 14 UINT _stdcall beginFun(LPVOID lParam) 15 { 16 cout << "beginthreadex" << endl; 17 return 0; 18 } 19 int main(void) 20 { 21 22 DWORD dwID; 23 UINT nID; 24 HANDLE hC; 25 HANDLE hB; 26 27 hC = CreateThread(NULL, 0, CreateFun, NULL, 0, &dwID); 28 29 if (NULL != hC) 30 { 31 CloseHandle(hC); 32 } 33 34 35 36 hB = (HANDLE)_beginthreadex(NULL, 0, beginFun, NULL, 0, &nID); 37 if (NULL != hB) 38 { 39 CloseHandle(hB); 40 } 41 42 Sleep(1000); 43 }
CloseHandle:关闭句柄
调用CloseHandle并不会终止线程的执行,而是递减线程内核对象句柄计数,线程执行完毕后也会自动递减,当计数为0时释放线程内核对象。当进程终止时也会清理内核对象。
但是,如果不关闭,可能导致有些进程拥有的资源无法释放,导致内存泄露。
线程的相关函数:
(1)CreateThread:创建线程,失败返回NULL,成功返回线程句柄
(2)SuspendThread:挂起线程
(3)ResumeThread:恢复线程
(4)OpenThread:打开线程,根据线程ID得到线程句柄
(5)ExitThread:退出线程
(6)TerminateThread:终止线程
(7)GetExitCodeThread:获取线程运行状态,如果为STILL_ALIVE表示正在运行。
(8)GetCurrentThread:获取当前线程句柄
(9)GetCurentThreadID:获取当前线程ID
注意:最好不要显式的调用ExitThread和TerminateThread,因为可能导致线程无法清理某些东西,导致内存泄露~