windows线程之CreateThread与_beginthreadex区别详解

CreateThread函数

  • CreateThread函数是用于创建线程的Windows函数。

CreateThread函数参数介绍

HANDLE CreateThread(
	LPSECURITY_ATTRIBUTES lpThreadAttributes,  
	DWORD dwStackSize,                         
	LPTHREAD_START_ROUTINE lpStartAddress,     
	LPVOID lpParameter,                        
	DWORD dwCreationFlags,                     
	DWORD* lpThreadId);                        
  • lpThreadAttributes :线程安全属性,一般设置为NULL
  • dwStackSizeL :指定线程可以为其线程栈使用多少地址空间。一般传入0,由编译器自行去分配。
  • lpStartAddress:线程函数地址
  • lpParameter:传递给线程函数的参数
  • dwCreationFlags:控制线程创建。如果值为0,线程创建后立即就进行调度。值为CREATE_SUSPENDED,系统将创建并初始化线程,但是会暂停该线程运行,这样它就无法进行调度。一般我们传入0
  • lpThreadId:存储系统分配给新线程的ID
  • 返回值为线程句柄

编程示例

#include <stdio.h>
#include <windows.h>

// 线程函数
DWORD WINAPI Thread(LPVOID lpParam)
{
	int number = (int)lpParam;

	//获取当前线程id
	DWORD threadId = GetCurrentThreadId();
	
	while (1) {
		printf("thread id is %d, number is %d\n", threadId, number--);
		Sleep(1000);
	}

	return 0;
}

int main()
{

	DWORD ThreadID1 = 0;
	DWORD ThreadID2 = 0;
	
	//创建线程
	HANDLE handle1 = CreateThread(NULL, 0, Thread, (LPVOID)(1024), 0, &ThreadID1);
	HANDLE handle2 = CreateThread(NULL, 0, Thread, (LPVOID)(1024), 0, &ThreadID2);


	printf("main function thread1 id is %d\n", ThreadID1);
	printf("main function thread2 id is %d\n", ThreadID2);

	//永久等待线程运行结束
	WaitForSingleObject(handle1, INFINITE);
	WaitForSingleObject(handle2, INFINITE);

	return 0;
}
  • 运行结果
main function thread1 id is 25072
main function thread2 id is 9260
thread id is 9260, number is 1024
thread id is 25072, number is 1024
thread id is 9260, number is 1023
thread id is 25072, number is 1023
thread id is 25072, number is 1022
thread id is 9260, number is 1022
thread id is 25072, number is 1021
thread id is 9260, number is 1021
thread id is 9260, number is 1020
thread id is 25072, number is 1020
...

_beginthreadex函数

  • _beginthreadex也可以创建一个线程

_beginthreadex函数参数介绍

unsigned long _beginthreadex(
	void *security, //线程安全属性
	unsigned stack_size, //线程栈大小
	unsigned (*start_address)(void*), //线程入口函数 
	void *arglist, //传递给线程的参数
	unsigned initflag, //控制线程创建,一般传入0
	unsigned *threaddr //线程id
)
  • 返回值为线程句柄

编程示例

#include <stdio.h>
#include <windows.h>
#include <process.h>

unsigned int __stdcall ThreadFun(PVOID lpParam)
{
	int number = (int)lpParam;

	//获取当前线程id
	DWORD threadId = GetCurrentThreadId();

	while (1) {
		printf("thread id is %d, number is %d\n", threadId, number--);
		Sleep(1000);
	}

	return 0;
}

int main()
{

	unsigned ThreadID1 = 0;
	unsigned ThreadID2 = 0;
	
	//创建线程
	HANDLE handle1 = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, (void*)1024, 0, &ThreadID1);
	HANDLE handle2 = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, (void*)1024, 0, &ThreadID2);

	printf("main function thread1 id is %d\n", ThreadID1);
	printf("main function thread2 id is %d\n", ThreadID2);

	//永久等待线程运行结束
	WaitForSingleObject(handle1, INFINITE);
	WaitForSingleObject(handle2, INFINITE);

	return 0;
}
  • 运行结果
main function thread1 id is 27340
main function thread2 id is 30424
thread id is 30424, number is 1024
thread id is 27340, number is 1024
thread id is 30424, number is 1023
thread id is 27340, number is 1023
thread id is 27340, number is 1022
thread id is 30424, number is 1022
thread id is 27340, number is 1021
thread id is 30424, number is 1021
thread id is 27340, number is 1020
thread id is 30424, number is 1020
thread id is 27340, number is 1019
thread id is 30424, number is 1019
thread id is 30424, number is 1018
thread id is 27340, number is 1018
...

CreateThread与 _beginthreadex区别

  • 结论:windows下创建线程时,一定不要调用操作系统的CreateThread函数,必须调用C/C++运行库函数_beginthreadex

历史介绍

  • 标准C运行库是1970年左右发明的,这个时候,还没有任何操作系统提供多线程的支持。因此标准C运行库的发明者根本没有考虑到为多线程应用程序提供C运行库的问题。

errno

  • 以标准C运行库的全局变量errno为例,有的函数会在出错时设置该变量。假定有这样一个程序段
BOOL fFailure = (system(NOTEPAD.exe README.txt) == -1)

if(fFailure){
	switch(errno){
		case E2BIG: //Argument list or environment too big
			break;
		case ENOENT://Command interpreter cannot be found
			break;
		case ENOEXEC://Command interpreter has bad format
			break;
		case ENOMEM://Insufficient memory to run command
			break;		
	}
}
  • 假设调用了system函数后,并在执行if语句之前,执行上述代码的线程被中断了。另外还假设,这个线程被中断后,同一个进程中的另一个线程开始执行,并且这个新线程将执行另一个C运行库函数,后者设置了全局变量errno。当CPU后来被分配回第一个线程时,对于上述代码中的system函数调用,errno反映的就不再时正确的错误码。
  • 为了解决这个问题,每个线程都需要它自己的errno变量。此外,必须有某种机制能够让一个线程引用它自己的errno变量,同时不能让它去修改另一个线程的errno变量。
  • 这仅仅是证明了"标准C/C++运行库最初不是为多线程应用程序而设计"的众多例子中的一个。在多线程环境中会出问题的C/C++运行库变量和函数有:errno,_doserrno,strtok,_wcstok,strerror,_strerror,tmpnam,tmpfile,asctime,_wasctime,gmtime,_ecvt,_fcvt等

_beginthreadex的优势

  • 为了保证C和C++多线程应用程序正常运行,必须创建一个数据结构,并使之与使用了C/C++运行库函数的每个线程关联。然后,在调用C/C++运行库函数时,那些函数必须知道去查找主调线程的数据块,从而避免影响到其他线程。
  • _beginthreadex伪代码
unitptr_t __cdecl _beginthreadex(void* psa, unsigned cbStackSize, unsigned (__stdcall* pfnStartAddr)(void*), void *pvParam, unsigned dwCreateFlag, unsigned* pdwThreadID){
	_ptiddata ptd;     //Pointer to thread`s data block
	uintptr_t thd1;    //Thread`s handle
	
	//Allocate data block for the new thread
	if((ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL){
		goto error_return;
	}
	
	// Initialize the data block
	initptd(ptd);
	
	//Save the desired thread function and the parameter
	//we want it to get in the data block
	ptd->_initaddr = (void*)pfnStartAddr;
	ptd->_initarg = pvParam;
	ptd->_thandle = (uintptr_t)(-1);
	
	//Create the new thread
	thd1 = (uintptr_t)CreateThread((LPSECURITY_ATTRIBUTES)psa, cbStackSize, _threadstartex, (PVOID)ptd, dwCreateFlag, pdwThreadID);
	if(thd1 == 0){
		//Thread couldn`t be created, cleanup and return failure
		goto error_return;
	}
	
	// Thread create OK, return the handle as unsigned long
	return(thd1);

	error_return:
		_free_crt(ptd);
		return((uintptr_t)0L);
}
  • 从伪代码可以看出,其实_beginthreadex就是对CreateThread进行了一个封装,但在调用CreateThread函数创建新线程前,申请了一个独立的_tiddata内存块,这个内存块与新线程进行了关联,这样就解决了之前例子中提到的让一个线程引用它自己的error变量并且不会修改另一个线程的error变量。

总结

  • CreateThread与_beginthreadex都是windows下创建线程的函数,但在使用时,一定要调用_beginthreadex而不能调用CreateThread
  • 标准C运行库发明时还没有多线程的概念,因此像全局变量errno,多个线程都能去修改它,导致它的最终值可能是不准确的,因此必须有一种机制,让一个线程只引用它自己的errno变量且不会修改其他线程的errno变量。_beginthreadex就是为每个线程申请了一块独立内存,这个内存与新线程进行了关联,每个线程只修改自己这块独立内存中的errno变量,且不会影响其他线程的errno变量。而CreateThread就没有这种机制,如果使用CreateThread创建线程,在设置errno全局变量或者调用一些线程不安全函数,比如strtok时,就会出问题。

参考

  • Windows核心编程(第五版)清华大学出版社
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值