_beginthreadex和CreateThread
_beginthreadex 和 _beginthread 是C++运行时库的函数
CreateThread是windows系统运行时库的函数
函数介绍
CreateThread
先从比较常见的CreatThread
说起,CreateThread函数创建一个在调用进程的地址空间内执行的线程。
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES 【lpThreadAttributes】, //指向线程安全属性的指针
DWORD 【dwStackSize】, //初始线程堆栈大小,以字节为单位
LPTHREAD_START_ROUTINE 【lpStartAddress】, //指向线程函数的指针
LPVOID 【lpParameter参数】, //参数新线程
DWORD 【dwCreationFlags】, //创建标志
LPDWORD 【lpThreadId】 //指向返回的线程标识符
);
返回值
如果函数成功,则返回值是新线程的句柄。
如果函数失败,返回值为NULL。要获取扩展错误信息,请调用GetLastError.
CreateThread
Demo:
/* 创建第一个线程。主进程结束,则撤销线程。 */
#include<Windows.h>
#include<stdio.h>
DWORD WINAPI ThreadFunc(LPVOID);
void main()
{
HANDLE hThread;
DWORD threadId;
hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threadId); // 创建线程
printf("我是主线程, pid = %d\n", GetCurrentThreadId()); //输出主线程pid
Sleep(2000);
}
DWORD WINAPI ThreadFunc(LPVOID p)
{
printf("我是子线程, pid = %d\n", GetCurrentThreadId()); //输出子线程pid
return 0;
}
终止线程
关于如何有效终止线程可以查看这篇文章👉使用WaitForSingleObject终止线程
《window核心编程》一书中提到,如要终止线程的运行,可以使用下面的方法:
- 线程函数返回(最好使用这种方法)
- 通过调用
ExitThread
函数,线程将自行撤销(但建议最好不要使用这种方法) - 同一个进程或另一个进程中的线程调用
TerminateThread
函数(应该避免使用这种方法)。 - 包含线程的进程终止运行。(这种方法也应该避免使用)
_beginthreadex
和 _beginthread
C++ 运行期库有两个创建线程的函数,一个是_beginthread
,另一个是 _beginthreadex
。
创建线程函数接口如下:
unsigned long _beginthread(
void(_cdecl *start_address)(void *), //声明为void (*start_address)(void *)形式 这个参数即为函数名
unsigned stack_size, //是线程堆栈大小,一般默认为0(表示与主线程使用一样的堆栈)
void *arglist //向线程传递的参数,一般为结构体,没有参数时设置为NULL
);
//推荐使用
unsigned long _beginthreadex(
void *security, //安全属性,NULL表示默认安全性
unsigned stack_size, //是线程堆栈大小,一般默认为0
unsigned(_stdcall *start_address)(void *), //声明为unsigned(*start_address)(void *)形式
void *argilist, //向线程传递的参数,一般为结构体
unsigned initflag, //新线程的初始状态,0表示立即执行,CREATE_SUSPEND表示创建后挂起。
unsigned *thrdaddr //该变量存放线程标识符,它是CreateThread函数中的线程ID。
};
线程结束:
//释放线程空间、释放线程TLS空间、调用ExiteThread结束线程。
void _endthread(void);
// retval:设定的线程结束码,与ExiteThread函数的参数功能一样,
//其实这个函数释放线程TLS空间,再调用ExiteThread函数,但没有释放线程空间。
void _endthreadex(unsigned retval);
两者的区别在于:
- 参数形式不同
ex
能够创建悬挂状态线程,在nt中能够指定级别,能够被thrdaddr
访问,因为它有了id
。ex
使用__stdcall
调用格式,必须返回exit code
。ex
返回0代表失败,原先的返回-1L。ex
创建的必须用ex销毁。
_beginthread
Demo:
Demo说明:
程序最后的system("pause")
必须有,因为_begeinThread
调用时,参数2的值为0
,即子线程是挂靠在主线程的,当主线程结束时,子线程也被迫结束,由于主线程执行时间过于短暂,因此需要使用该语句控制线程处于暂停状态,另外,由于三个线程的问题,为了控制打印的次序,在线程中也加了Sleep()
函数控制执行的时刻。
例子也包括了_begeinThread
如何传参。
#include <iostream>
#include <windows.h>
#include <process.h>
#include <string.h>
using std::cout;
using std::endl;
typedef struct _STU_PEOPLE
{
char szName[50];
int age;
}STU_PEOPLE,*PSTU_PEOPLE;
void thread_1(void *)
{
cout << "调用了线程 thread_1 无进入参数" << endl;
}
void thread_2(void *lpVoid)
{
Sleep(10);
int pnInt = (int)lpVoid;
cout << "调用了线程 thread_2 进入参数为:" << pnInt << endl;
}
void thread_3(void *lpVoid)
{
Sleep(50);
PSTU_PEOPLE pnInt = (PSTU_PEOPLE)lpVoid;
cout << "调用了线程 thread_3 进入参数为:" << pnInt->szName << ':' << pnInt->age << endl;
}
int main()
{
STU_PEOPLE stuChild;
char name[] = { "Child_LI" };
strcpy_s(stuChild.szName, name);
stuChild.age = 6;
std::cout << "Start Thread!\n";
_beginthread(thread_1, 0, NULL);
_beginthread(thread_2, 0, (void*)stuChild.age);
_beginthread(thread_3, 0, (void*)&stuChild);
std::cout << "End Thread!\n";
Sleep(100);
system("pause");
}
输出:
需要注意的是,
Sleep()
函数并不能保证线程严格按照规定的时间运行。1. 当我们用
_beginthread()
函数创建一个线程之后,这个线程将马上伺机执行,但是需要等待CPU为其分配资源,线程执行的顺序是不一定的(完全有可能最先创建的线程最后执行);2. main函数是主线程函数,在main函数中创建的线程为子线程。在主线程结束后子线程将被迫停止,因此子线程实际上不会被执行;
3. 为了执行子线程,可以将主线程一直运行,如
while(1);
或者system("pause")
; 因此在写项目代码时,一定不要忘记在线程的函数中添加while(1)
循环也可以将某个线程强制休眠,用
Sleep()
函数。Sleep()
函数将指定某个线程至少休眠多长时间。
区分
在 Win32 API 中,创建线程的基本函数是 CreateThread
,而 _beginthread(ex)
是 C++ 运行库的函数。为什么要有两个呢?
因为C++ 运行库里面有一些函数使用了全局 量,如果使用 CreateThread 的情况下使用这些C++ 运行库的函数,就会出现不安全 的问题。而 _beginthreadex 为这些全局变量做了处理,使得每个线程都有一份独立 的“全局”量。
所以,如果你的编程只调用 Win32 API/SDK ,就放心用 CreateThread
;如果要用到 C++ 运行时间库,那么就要使用_beginthreadex
,并且需要在编译环境中选择 Use MultiThread Lib/DLL。
参考:
- Win32API手册
- C++手册
- 网络文章