1. 概述
CreateThread
: Windows的API函数(SDK函数的标准形式,直截了当的创建方式,任何场合都可以使用),提供操作系统级别的创建线程的操作,且仅限于工作者线程(一般不建议使用)_beginthread
、_beginthreadex
:- MS对C Runtime库的扩展SDK函数,首先针对C Runtime库做了一些初始化的工作,以保证C Runtime库工作正常,
- 然后,调用
CreateThread
真正创建线程。 _beginthread
是_beginthreadex
的功能子集,- 虽然
_beginthread
内部是调用_beginthreadex
,但它屏蔽了安全特性这样的功能
- 例如,如果使用
_beginthread
,就无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程的ID值。
- 例如,如果使用
_beginthread
与CreateThread
不是同等级别,_beginthreadex
和CreateThread
在功能上完全可替代
AfxBeginThread
:MFC中线程创建的MFC函数,它简化了操作或让线程能够响应消息,即可用于界面线程,也可以用于工作者线程,但要注意尽量不要在一个MFC程序中使用_beginthreadex()或CreateThread()。AfxBeginThread
、_beginThread
和_beginThreadex
实际上是编译器对CreateThread
的封装
1. 编程的时候如何选择各个函数
MFC程序选择
AfxBeginThread
如果不使用Microsoft的Visual C++编译器,你的编译器供应商有它自己的CreateThred替代函数
2. 尽量不调用CreateThread
尽量不要调用
CreateThread
。相反,应该使用Visual C++运行期库函数_beginthreadex
,原因如下:- 考虑标准C运行时库的一些变量和函数,如
errno
,这是一个全局变量。全局变量用于多线程会出现资源竞争问题,需要进行同步互斥。故必须存在一种机制,使得每个线程能够引用它自己的errno变量,又不触及另一线程的errno变量,_beginthreadex
就为每个线程分配自己的tiddata
内存结构:
- 该结构保存了许多像
errno
这样的变量和函数的值、地址。 - 通过线程局部存储将
tiddata
与线程联系起来。具体实现在Threadex.c中有。 - 结束线程使用函数
_endthreadex
函数,释放掉线程的tiddata
数据块。
- 该结构保存了许多像
(以下主要介绍
CreateThread
会导致内存泄漏)CRT的函数库在线程出现之前就已经存在,所以原有的CRT不能真正支持线程,这导致我们在编程的时候有了CRT库的选择, 在MSDN中查阅CRT的函数时都有:Libraries LIBC.LIB Single thread static library, retail version LIBCMT.LIB Multithread static library, retail version MSVCRT.LIB Import library for MSVCRT.DLL, retail version
这也导致了许多CRT的函数在多线程的情况下必须有特殊的支持,不能简单的使用
CreateThread
就行。 大多的CRT函数都可以在CreateThread
线程中使用,看资料说只有signal()
函数不可以,会导致进程终止。但其他大多数函数可以用并不是说没有问题, 有些CRT的函数像malloc()
,fopen()
,_open()
,strtok()
,ctime()
, 或localtime()
等函数需要专门的线程局部存储的数据块,这个数据块通常需要在创建线程的时候就建立,如果使用CreateThread
,这个数据块就没有建立,在这样的线程中还是可以使用这些函数而且没有出错,实际上函数发现这个数据块的指针为空时,会自己建立一个,然后将其与线程联系在一起,这意味着如果你用CreateThread
来创建线程,然后使用这样的函数,会有一块内存在不知不觉中创建,遗憾的是,这些函数并不将其删除,而CreateThread
和ExitThread
也无法知道这件事,于是就会有Memory leak,在线程频繁启动的软件中(比如某些服务器软件),迟早会让系统的内存资源耗尽!_beginthreadex
和_endthreadex
就对这个内存块做了处理,所以没有问题。如果在除主线程之外的任何线程中进行一下操作,你就应该使用多线程版本的C runtime library,并使用
_beginthreadex
和_endthreadex
:- 使用
malloc()
和free()
,或是new
和delete
- 使用
stdio.h
或io.h
里面声明的任何函数 - 使用浮点变量或浮点运算函数
- 调用任何一个使用了静态缓冲区的
runtime函数
,比如:asctime()
,strtok()
或rand()
Handle的问题,
_beginthread
的对应函数_endthread
自动的调用了CloseHandle
,而_beginthreadex
的对应函数_endthreadex
则没有,所以CloseHandle
无论如何都是要调用的不过_endthread
可以帮你执行自己不必写,其他两种就需要自己写!(Jeffrey Richter强烈推荐尽量不用显式的终止函数,用自然退出的方式,自然退出当然就一定要自己写CloseHandle)- 使用
- 考虑标准C运行时库的一些变量和函数,如
3. 注意
1)C++主线程的终止,同时也会终止所有主线程创建的子线程,不管子线程有没有执行完毕。所以如果不调用WaitForSingleObject
,则子线程可能并没有执行完毕或根本没有执行。
2)如果某线程挂起,然后有调用WaitForSingleObject
等待该线程,就会导致死锁。所以上面的代码如果不调用resumethread
,则会死锁。
4. _beginthreadex
用法
头文件:
process.h
函数原型:
unsigned long _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( __stdcall *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
//第1个参数:安全属性,NULL为默认安全属性
//第2个参数:指定线程堆栈的大小。如果为0,则线程堆栈大小和创建它的线程的相同。一般用0
//第3个参数:指定线程函数的地址,也就是线程调用执行的函数地址(用函数名称即可,函数名称就表示地址,注意的是函数访问方式一定是__stdcall,函数返回值一定是unsigned,函数参数一定是void*)
//第4个参数:传递给线程的参数的指针,可以通过传入对象的指针,在线程函数中再转化为对应类的指针
//第5个参数:线程初始状态,0:立即运行;CREATE_SUSPEND:悬挂(如果出事状态定义为悬挂,就要调用ResumeThread(HANDLE) 来激活线程的运行)
//第6个参数:用于记录线程ID的地址
5.示例
- 示例1:
#include <iostream>
#include <string>
#include <process.h>
#include <Windows.h>
using namespace std;
class ThreadX{
private:
int loopStart;
int loopEnd;
int dispFrequency;
public:
string threadName;
ThreadX(int startVal, int endVal, int frequency){
this->loopStart = startVal;
this->loopEnd = endVal;
this->dispFrequency = frequency;
}
static unsigned __stdcall ThreadStaticEntryPoint(void * pThis){
ThreadX* pThX = (ThreadX*) pThis;
pThX->ThreadEntryPoint();
return 1;
}
void ThreadEntryPoint(){
for (int i = loopStart; i <= loopEnd; i++){
cout << threadName << " i = " << i << endl;
Sleep(100);
}
}
};
int main(){
ThreadX * pThX = new ThreadX(0, 10, 2000);
HANDLE hth1 = NULL;
unsigned uiThread1ID;
hth1 =(HANDLE) _beginthreadex(NULL,
0,
ThreadX::ThreadStaticEntryPoint,
pThX,
CREATE_SUSPENDED,
&uiThread1ID);
if (hth1 == NULL){
cout << "failed to create thread 1" << endl;
}
DWORD dwExitCode;
GetExitCodeThread(hth1, &dwExitCode); // shoule be STILL_ACTIVE
cout << "init thread 1 exit code id = " << dwExitCode << endl;
pThX->threadName = "yang1";
ThreadX* pThX2 = new ThreadX(0, 10, 2000);
HANDLE hth2 = NULL;
unsigned uiThread2ID;
hth2 = (HANDLE) _beginthreadex(NULL,
0,
ThreadX::ThreadStaticEntryPoint,
pThX2,
CREATE_SUSPENDED,
&uiThread2ID);
if (hth2 == NULL){
cout << "create thread 2 failed" << endl;
}
GetExitCodeThread(hth2, &dwExitCode);
cout << "init thread 2 exit code id = " << dwExitCode << endl;
pThX2->threadName = "yang2";
ResumeThread(hth1);
ResumeThread(hth2);
WaitForSingleObject(hth1, INFINITE);
WaitForSingleObject(hth2, INFINITE);
GetExitCodeThread(hth1, &dwExitCode);
cout << "thread 1 exited with exit code " << dwExitCode << endl;
GetExitCodeThread(hth2, &dwExitCode);
cout << "thread 2 exited with exit code " << dwExitCode << endl;
CloseHandle(hth1);
CloseHandle(hth2);
delete pThX;
delete pThX2;
pThX = NULL;
pThX2 = NULL;
cout << "end program " << endl;
return 0;
}
- 示例2:
#include<string>
#include<iostream>
#include<process.h>
#include<windows.h>
using namespace std;
struct Arg//用来传参给线程函数
{
double d_;
string str_;
Arg(double dd, string ss):d_(dd), str_(ss){}
};
//线程绑定的函数返回值和参数是确定的,而且一定要__stdcall
unsigned __stdcall threadFun(void *)
{
for(int i = 0; i < 10; i++)
cout<<i<<endl;
return 1;
}
//可以通过结构体来传入参数
unsigned __stdcall threadFunArg(void *arglist)
{
Arg *p = (Arg *)arglist;
cout<<p->d_<<endl;
cout<<p->str_<<endl;
return 2;
}
//简单的线程类
class ThreadClass
{
private:
string str_;
int i_;
public:
ThreadClass(string s, int i):str_(s), i_(i){}
static unsigned __stdcall threadStaic(void *arg)
{
ThreadClass *p = (ThreadClass *)arg;
p->threadfun();
return 3;
}
void threadfun()
{
cout<<str_<<endl;
cout<<i_<<endl;
}
};
int main()
{
unsigned int thID1, thID2, thID3, thID4;
HANDLE hth1, hth2, hth3, hth4;
Arg arg(3.14, "hello world");
ThreadClass tclass("welcom", 999);
//注意的是_beginthreadex是立即返回的,系统不会等线程函数执行完毕,因此要保证
//局部arg变量 在线程函数执行完毕前不会释放,更安全的是使用new来构造arg
hth1 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, &thID1);
hth2 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, &thID2);
hth3 = (HANDLE)_beginthreadex(NULL, 0, threadFunArg, &arg, 0, &thID3);
hth4 = (HANDLE)_beginthreadex(NULL, 0, ThreadClass::threadStaic, &tclass, 0,
&thID4);
//主线程一定要等待子线程结束
WaitForSingleObject(hth1, INFINITE);
WaitForSingleObject(hth2, INFINITE);
WaitForSingleObject(hth3, INFINITE);
WaitForSingleObject(hth4, INFINITE);
DWORD exitCode1, exitCode2, exitCode3, exitCode4;
GetExitCodeThread(hth1, &exitCode1);
GetExitCodeThread(hth2, &exitCode2);
GetExitCodeThread(hth3, &exitCode3);
GetExitCodeThread(hth4, &exitCode4);
cout<<endl<<"exitcode::"<<exitCode1<<" "<<exitCode2<<" "<<exitCode3<<" "
<<exitCode4<<endl;
cout<<"ID:"<<thID1<<" "<<thID2<<" "<<thID3<<" "<<thID4<<endl;
//一定要记得关闭线程句柄
CloseHandle(hth1);
CloseHandle(hth2);
CloseHandle(hth3);
CloseHandle(hth4);
}