首页 > 其他分享 >C语言 - 使用_beginthreadex()创建线程

C语言 - 使用_beginthreadex()创建线程

时间:2023-10-13 11:02:27浏览次数:29  
标签:int beginthreadex printf unsigned C语言 线程 句柄

经过了解才知道,C++03之前,用的创建线程都是CreateThread 与 _beginthreadex。使用这个两个函数进行创建线程。
然后C++11之后,就出现了新的线程函数thread,当然,这个创建线程比较方便!

经过两三天的纠结,最终决定深入研究_beginthreadex此方式创建线程,具体为什么我也说不清楚,看到网上很多人都推荐使用这个。。。

反正_beginthreadex内部都是调用CreateThread 进行创建线程的!

 

_beginthreadex函数原型:

unsigned long _beginthreadex(
    
    void *security,    		// 安全属性, 为NULL时表示默认安全性
 
    unsigned stack_size,    // 线程的堆栈大小, 一般默认为0
 
    unsigned(_stdcall *start_address)(void *),   // 线程函数
 
    void *argilist, 		// 线程函数的参数
 
    unsigned initflag,    	// 新线程的初始状态,0表示立即执行,//CREATE_SUSPENDED表示创建之后挂起
   
    unsigned *threaddr    	// 用来接收线程ID
 
);

返回值 

成功:返回新线程句柄,

失败:返回0

 

1. _beginthreadex创建线程

创建线程:

int iParam = 10;
unsigned int dwThreadID;	// 接收线程的ID
HANDLE hThread;				// 线程句柄

// 创建线程
hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, (void *)&iParam, 0, &dwThreadID);


// 线程执行的函数
unsigned int WINAPI  ThreadFun(LPVOID p) {

	int cnt = *((int *)p);
	// ......

	return 0;
}

第一第二个参数,默认写NULL和0就行了,第三个参数是线程执行的函数,第四个参数是函数的参数,第五个参数是指定线程立刻执行,第六个参数是获得线程的ID。

可以判断一下线程句柄,是否创建线程成功:

if (hThread == NULL) 
{
	printf("_beginthreadex() Error!\n");
	return -1;
}

如果函数需求是传入多个参数,那么只能使用结构体了!

然后线程执行完毕后要记得关闭线程句柄:

CloseHandle(hThread);

到此,一个简单的线程就已经创建完毕了!

测试代码:

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

unsigned int WINAPI  ThreadFun(LPVOID p) {

	int cnt = *((int *)p);
	for (int i = 0; i < cnt; i++) {
		Sleep(1000);
		printf("Running thread!\n");
	}

	return 0;
}

int main(void) {
	int iParam = 10;
	unsigned int dwThreadID;	// 线程id

	printf("main begin\n");

	// 线程句柄				// 创建线程
	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, (void *)&iParam, 0, &dwThreadID);

	if (hThread == NULL) {
		printf("_beginthreadex() Error!\n");
		return -1;
	}
	
	// 关闭
	CloseHandle(hThread);

	printf("main end\n");
	//Sleep(5000);
	system("pause");
	return 0;
}

运行结果:

 

 注意:代码中必须加上 system(“pause”);让主线程暂停在哪里等待子线程执行完毕,否则主线程执行完毕就直接接收程序了,子线程完全没有机会执行!
这里的mian函数就是主线程也就是进程,当进程结束后,会结束掉所有线程!

 

2. 单线程句柄阻塞

上面第二点,如果我想等子线程执行完了之后再接着继续往下走执行printf(“main end\n”);

这需要怎么操作呢?

这就要用到阻塞了,就是阻塞主线程,子线程执行完毕后,再接着执行主线程。

使用这个函数:

WaitForSingleObject:等待一个内核对象变为已通知状态

WaitForSingleObject(
    _In_ HANDLE hHandle,    	  //指明一个内核对象的句柄
    _In_ DWORD dwMilliseconds     //等待时间
);

这样使用:

DWORD wr;
	
// 等待通知,等待子线程执行完毕
// 阻塞主线程,INFINITE:等待特定秒数;WAIT_FAILED:WaitForSingleObject等待结束后会返回它
printf("WaitForSingleObject() begin\n");
if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED) {
	printf("Thread wait error!\n");
	return -1;
}
printf("WaitForSingleObject() end\n");

WaitForSingleObject传入两个参数,参数一是线程句柄,参数二是等待时间,INFINITE表示很多秒,具体多少秒不知道,但是足以应付日常线程使用,也可以指定具体秒数。
如果发生错误会返回WAIT_FAILED,这样进行判断就可以知道等待是有没有发生错误了!

测试代码:

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

// 线程执行的函数
unsigned int WINAPI  ThreadFun(LPVOID p) {

	int cnt = *((int *)p);
	for (int i = 0; i < cnt; i++) {
		Sleep(1000);
		printf("Running thread!\n");
	}

	return 0;
}

int main(void) {
	int iParam = 10;
	unsigned int dwThreadID;	// 线程id
	DWORD wr;	// 阻塞线程返回值

	printf("main begin\n");

	// 线程句柄				// 创建线程
	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, (void *)&iParam, 0, &dwThreadID);

	if (hThread == NULL) {
		printf("_beginthreadex() Error!\n");
		return -1;
	}

	// 等待通知,等待子线程执行完毕
	// 阻塞主线程,INFINITE:等待特定秒数;WAIT_FAILED:WaitForSingleObject等待结束后会返回它
	printf("WaitForSingleObject() begin\n");
	if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED) {
		printf("Thread wait error!\n");
		return -1;
	}
	printf("WaitForSingleObject() end\n");

	CloseHandle(hThread);

	printf("main end\n");
	//Sleep(5000);
	//system("pause");	// 不需要再暂停了
	return 0;
}

运行结果:

 

3. 多个线程句柄阻塞

提一个需求,创建n个线程,然后循环n次,奇数次对一个全局变量做加一操作,偶数次对一个全局变量做减一操作。
按照正常来讲,设置这个全局变量为零,当程序结束,这个全局变量也还是零才对,事实上是这样嘛?

使用WaitForMultipleObjects阻塞多个线程

WaitForMultipleObjects(
    _In_ DWORD nCount,    		// 要监测的句柄的组的句柄的个数
    _In_reads_(nCount) CONST HANDLE* lpHandles,   //要监测的句柄的组
    _In_ BOOL bWaitAll,  		// TRUE 等待所有的内核对象发出信号, FALSE 任意一个内核对象发出信号
    _In_ DWORD dwMilliseconds 	//等待时间
);

使用如下:

// 阻塞多个线程句柄,直到子线程运行完毕,主线程才会往下走
WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);

参数一:检测句柄的个数;

参数二:检测句柄的数组;

参数三:TRUE等待所有线程执行完毕,FALSE,任意一个完成就停止阻塞;

参数四:等待时间

 

测试代码:

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

#define NUM_THREAD		50

unsigned WINAPI threadInc(void *arg);	// 减一操作
unsigned WINAPI threadDes(void *arg);	// 加一操作

// 操作的全局变量
long long num = 0;

int main(void) {
	// 创建n个线程句柄
	HANDLE tHandles[NUM_THREAD];
	
	printf("sizeof long long :%d\n", sizeof(long long));

	// 循环n次,创建n个线程
	for (int i = 0; i < NUM_THREAD; i++) {
		if (i % 2) {
			tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		
		} else {
			tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
		}
	}

	// 阻塞多个线程句柄,直到子线程运行完毕,主线程才会往下走
	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);

	for (int i = 0; i < NUM_THREAD; i++) {
		CloseHandle(tHandles[i]);
	}

	printf("result:%lld\n", num);
	return 0;
}


// 对全局变量加一操作
unsigned WINAPI threadInc(void *arg) {
	for (int i = 0; i < 500000; i++) {
		num += 1;
	}

	return 0;
}


// 对全局变量减一操作
unsigned WINAPI threadDes(void *arg) {
	for (int i = 0; i < 500000; i++) {
		num -= 1;
	}

	return 0;
}

运行结果:

可以看到,最终结果不是零,而是乱值,这是为什么???

以上图为例,假设全局变量num是99,线程一开始执行,获取它做加一操作,然后还没等线程一将数值还回去,线程二又获取它做操作,然后还回去,最后线程一才将数值还回去,所以导致计算结果不正确。
这里这是举一个例子啊,大致上表达的意思就是线程一和线程二公用同一块内存变量,导致数值计算不正确。

如果想要解决这个问题,那么必须使用:互斥对象CreateMutex,这个将在下一篇博客中讲解用法!

————————————————
版权声明:本文为CSDN博主「cpp_learners」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/cpp_learner/article/details/120415489

 

标签:int,beginthreadex,printf,unsigned,C语言,线程,句柄
From: https://www.cnblogs.com/zhuchunlin/p/17761580.html

相关文章

  • c语言代码练习(字节序列)-28
    需求:写一段代码高数我们当前机#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>intsheck_sys(){inta=1;return*(char*)&a;}intmain(){intret=sheck_sys();if(ret==1){printf("小端");}else......
  • C语言 - 内联函数
    在C语言中,内联函数(InlineFunction)是一种用于优化代码执行效率的机制。内联函数在编译时将函数的代码直接插入到调用它的地方,而不是通过函数调用的方式执行,从而减少了函数调用的开销,提高了代码的执行速度。C语言的内联函数使用inline关键字来声明。将函数声明为内联函数只是给......
  • 学习C语言心得--传址调用
    传址调用#include<stdio.h>voidswep(int*x,int*y){ intp=0; p=*x; *x=*y; *y=p;}intmain(){ inta=10; intb=20; printf("交换前:%d%d",a,b); swep(&a,&b); printf("交换后:%d%d",a,b); return0;}运行结果:......
  • C语言函数和指针的关系之三(完结)
     指针保存函数的地址(函数指针)1、函数指针的概念:咱们定义的函数,在运行程序的时候,会将函数的指令加载到内存的代码段。所以函数也有起始地址。c语言规定:函数的名字就是函数的首地址,即函数的入口地址咱们就可以定义一个指针变量,来存放函数的地址。这个指针变量就是函数指针变量......
  • Linux C语言Shared Library共享库细节探究
    开发中遇到一个问题,比如有一个类库A,被类库B引用,类库B和类库A都被程序C引用。类库A中有一个全局变量G,要求同一个进程中使用的是同一个全局变量G。虽然看起来很简单,但是实际探究下来还有不少坑。如果不是类库如果AB都不是类库,而是直接引入源码编译,理论上比较方便解决。示例一p......
  • 面试官:ConcurrentHashMap 是如何保证线程安全的
    1、前言阅读此篇文章,你需要有以下知识基础Java内存模型,可见性问题CASHashMap底层原理我们知道,在日常开发中使用的HashMap是线程不安全的,而线程安全类HashTable只是简单的在方法上加锁实现线程安全,效率低下,所以在线程安全的环境下我们通常会使用ConcurrentHashMap,但是又为何需要学习......
  • 关于c语言操作libwebsockets示例
    第一步,安装libwebsockets库,c语言编写的,默认安装引用库,配置相应的库及路径第二步:上代码main.h ////CreatedbyAdministratoron2020/5/1.// #ifndefMEDIA_MAIN_H#defineMEDIA_MAIN_H #define boolchar volatileintexit_sig=0; #defineMAX_PAYLOAD_SIZE 10......
  • Java 线程池
    目录线程池线程池创建方式通过ThreadPoolExecutor创建线程池ThreadPoolExecutor的总体设计ThreadPoolExecutor的继承关系ThreadPoolExecutor的运行机制ThreadPoolExecutor生命周期管理任务执行机制线程池线程池就是管理一系列线程的资源池。当有任务要处理时,直接从线程池......
  • C语言函数和指针的关系之二(未完)
    指针作为函数的返回值一个函数可以返回整型数据、字符数据、浮点型的数据,也可以返回一个指针.例30:char*fun(){charstr[100]="helloworld";returnstr;}intmain(){char*p;p=fun();printf("%s\n",p);//}//总结:返回地址的时候,地址指向的内存的内容不能释放如果返回......
  • [C语言快速入门] 基础知识和基本数据类型
    [C语言快速入门]基础知识和基本数据类型这里主要的知识点主要来自这两个教学视频:史上最强最细腻的linux嵌入式C语言学习教程【李慧芹老师】_哔哩哔哩_bilibiliC语言基础入门_C3程序猿_哔哩哔哩_bilibili这本书:《CPrimerPlus(第6版)中文版》在本文开篇之前,想简单写一下关于......