9.1 运用API创建多线程
在Windows平台下创建多线程有两种方式,读者可以使用CreateThread函数,或者使用beginthreadex函数均可,两者虽然都可以用于创建多线程环境,但还是存在一些差异的,首先CreateThread函数它是Win32 API的一部分,而_beginthreadex是C/C++运行库的一部分,在参数返回值类型方面,CreateThread返回线程句柄,而_beginthreadex返回线程ID,当然这两者在使用上并没有太大的差异,但为了代码更加通用笔者推荐使用后者,因为后者与平台无关性更容易实现跨平台需求。
9.1.1 CreateThread
CreateThread 函数是Windows API提供的用于创建线程的函数。它接受一些参数,如线程的入口函数、线程的堆栈大小等,可以创建一个新的线程并返回线程句柄。开发者可以使用该句柄控制该线程的运行状态。需要注意,在使用CreateThread创建线程时,线程入口函数的返回值是线程的退出码,而不是线程执行的结果值。
CreateThread 函数原型如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
参数说明:
- lpThreadAttributes:指向
SECURITY_ATTRIBUTES结构体的指针,指定线程安全描述符和访问权限。通常设为NULL,表示使用默认值。 - dwStackSize:指定线程堆栈的大小,以字节为单位。如果
dwStackSize为0,则使用默认的堆栈大小。(注:在32位程序下,该值的默认大小为1MB;在64位程序下,该值的默认大小为4MB) - lpStartAddress:指向线程函数的指针,这个函数就是线程执行的入口点。当线程启动时,系统就会调用这个函数。
- lpParameter:指定传递给线程函数的参数,可以为NULL。
- dwCreationFlags:指定线程的创建标志。通常设为0,表示使用默认值。
- lpThreadId:指向一个
DWORD变量的指针,表示返回的线程ID号。可以为NULL。
CreateThread 函数将创建一个新的线程,并返回线程句柄。开发者可以使用该句柄控制该线程的运行状态,如挂起、恢复、终止等。线程创建成功后,执行线程函数进行相应的业务处理。需要注意的是,在使用CreateThread创建线程时,线程入口函数的返回值是线程的退出码,而不是线程执行的结果值。
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Func(LPVOID lpParamter)
{
for (int x = 0; x < 10; x++)
{
cout << "thread function" << endl;
Sleep(200);
}
return 0;
}
int main(int argc,char * argv[])
{
HANDLE hThread = CreateThread(NULL, 0, Func, NULL, 0, NULL);
CloseHandle(hThread);
for (int x = 0; x < 10; x++)
{
cout << "main thread" << endl;
Sleep(400);
}
system("pause");
return 0;
}
如上所示代码中我们在线程函数Func()内没有进行任何的加锁操作,那么也就会出现资源的争夺现象,这些会被抢夺的资源就被称为是临界资源,我们可以通过设置临界锁来实现同一时刻内保持一个线程操作资源。
EnterCriticalSection 是Windows API提供的线程同步函数之一,用于进入一个临界区并且锁定该区域,以确保同一时间只有一个线程访问临界区代码。
EnterCriticalSection函数的函数原型如下:
void EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
参数说明:
- lpCriticalSection:指向CRITICAL_SECTION结构体的指针,表示要进入的临界区。
EnterCriticalSection 函数将等待,直到指定的临界区对象可用并且已经锁定,然后,当前线程将进入临界区。临界区中的代码将在当前线程完成之前,不允许被任何其他线程执行。当线程完成临界区的工作时,应该调用LeaveCriticalSection函数释放临界区。否则,其他线程将无法进入临界区,导致死锁。
EnterCriticalSection 函数是比较底层的线程同步函数,需要开发者自行创建临界区,维护临界区的状态并进行加锁解锁的操作,使用时需要注意对临界区中的操作进行适当的封装和处理。同时,EnterCriticalSection函数也是比较高效的线程同步方式,对于需要频繁访问临界资源的场景,可以通过使用临界区来提高程序的性能。
#include <Windows.h>
#include <iostream>
int Global_One = 0;
// 全局定义临界区对象
CRITICAL_SECTION g_cs;
// 定义一个线程函数
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
// 加锁防止线程数据冲突
EnterCriticalSection(&g_cs);
for (int x = 0; x < 10; x++)
{
Global_One++;
Sleep(1);
}
// 执行完修改以后,需要释放锁
LeaveCriticalSection(&g_cs);
return 0;
}
int main(int argc, char * argv[])
{
// 初始化临界区
InitializeCriticalSection(&g_cs);
HANDLE hThread[10] = { 0 };
for (int x = 0; x < 10; x++)
{
// 循环创建线程
hThread[x] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
}
// 等待多个线程执行结束
WaitForMultipleObjects(10, hThread, TRUE, INFINITE);
// 最后循环释放资源
for (int x = 0; x < 10; x++)
{
CloseHandle(hThread[x]);
}
printf("全局变量值: %d \n", Global_One);
// 释放锁
DeleteCriticalSection(&g_cs);
system("pause");
return 0;
}
9.1.2 BeginThreadex
BeginThreadex 是C/C++运行库提供的用于创建线程的函数。它也接受一些参数,如线程的入口函数、线程的堆栈大小等,与CreateThread不同的是,_beginthreadex函数返回的是线程的ID,而不是线程句柄。开发者可以使用该ID在运行时控制该线程的运行状态。此外,_beginthreadex函数通常与_endthreadex配对使用,供线程退出时使用。
beginthreadex 函数的函数原型如下:
uintptr_t _beginthreadex(
void* security,
unsigned stack_size,
unsigned(__stdcall* start_address)(void*),
void* arglist,
unsigned initflag,
unsigned* thrdaddr
);
参数说明:
- security:与
Windows安全机制相关,用于指定线程的安全属性,一般填NULL即可。 - stack_size:指定线程的堆栈大小,以字节为单位。如果
stack_size为0,则使用默认的堆栈大小。 - start_address:线程函数的入口点。
- arglist:传递给线程函数的参数。
- initflag:线程标志,0表示启动线程后立即运行,
CREATE_SUSPENDED表示启动线程后暂停运行。 - thrdaddr:指向
unsigned变量的指针,表示返回的线程ID号。可以为NULL。
与CreateThread相比,_beginthreadex函数返回线程ID而非线程句柄,使用时需要注意区分。与CreateThread不同的是,_beginthreadex函数接受传递给线程函数的参数放在arglist中,方便传递多个参数。线程使用完需要调用_endthreadex函数来关闭线程。当使用了_beginthreadex创建的线程退出时,会调用_endthreadex来结束线程,这里的返回值会被当做线程的退出码。
#include <windows.h>
#include <iostream>
#include <process.h>
using namespace std;
unsigned WINAPI Func(void *arg)
{
for (int x = 0; x < 10; x++)
{
cout << "thread function" << endl;
Sleep(200);
}
return 0;
}
int main(int argc, char * argv[])
{
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, Func, NULL, 0, NULL);
CloseHandle(hThread);
for (int x = 0; x < 10; x++)
{
cout << "main thread" << endl;
Sleep(400);
}
system("pause");
return 0;
}
由于CreateThread()函数是Windows提供的API接口,在C/C++语言另有一个创建线程的函数_beginthreadex()该函数在创建新线程时会分配并初始化一个_tiddata块,这个块用来存放一些需要线程独享的数据,从而保证了线程资源不会发生冲突的情况,代码只需要稍微在上面基础上改进即可。
当然该函数同样需要设置线程临界区而设置方式与CreateThread中所展示的完全一致。
#include <stdio.h>
#include <process.h>
#include <windows.h>
// 全局资源
long g_nNum = 0;
// 子线程个数
const int THREAD_NUM = 10;
CRITICAL_SECTION g_csThreadCode;
unsigned int __stdcall ThreadFunction(void *ptr)
{
int nThreadNum = *(int *)ptr;
// 进入线程锁
EnterCriticalSection(&g_csThreadCode);
g_nNum++;
printf("线程编号: %d --> 全局资源值: %d --> 子线程ID: %d \n", nThreadNum, g_nNum, GetCurrentThreadId());
// 离开线程锁
LeaveCriticalSection(&g_csThreadCode);
return 0;
}
int main(int argc,char * argv[])
{
unsigned int ThreadCount = 0;
HANDLE handle[THREAD_NUM];
InitializeCriticalSection(&g_csThreadCode);
for (int each = 0; each < THREAD_NUM; each++)
{
handle[each] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, &each, 0, &ThreadCount);
printf("线程ID: %d \n", ThreadCount);
}
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
DeleteCriticalSection(&g_csThreadCode);
system("pause");
return 0;
}
总的来说,_beginthreadex比CreateThread更加高级,封装了许多细节,使用起来更方便,特别是对于传递多个参数的情况下,可以更简单地传参。
本文作者: 王瑞
本文链接: https://www.lyshark.com/post/922df2e6.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
9.1 运用API创建多线程的更多相关文章
- _beginThreadex创建多线程解读【转】
_beginThreadex创建多线程解读 一.需要的头文件支持 #include <process.h> // for _beginthread() 需要的设置:Proj ...
- 转载自~浮云比翼:Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)
Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥) 介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可 ...
- 转:MFC中创建多线程
MFC中创建多线程 MFC的多线程函数必须声明为静态的或者是全局函数(不同的在于全局函数不能访问类的私有静态成员,而静态类函数可以):但这样的线程函数只能访问静态的成员变量,要实现访问类的其他成员 ...
- 转:MFC创建多线程实例
作者:http://blog.csdn.net/wangningyu/article/details/4404134 平时在MFC里使用多线程时其实是很方面的,因为微软提供了一个API让我们很方面的去 ...
- _beginThreadex创建多线程解读
_beginThreadex创建多线程解读 一.须要的头文件支持 #include <process.h> // for _beginthread() 须要的设置:Proj ...
- java中创建多线程的方式
在java中比较常用的有三种创建多线程的方式. 方式一:继承Thread类,要重写run方法. 在MyThread类 public class MyThread extends Thread { @O ...
- 使用_beginThreadex创建多线程(C语言版多线程)
_beginThreadex创建多线程解读 一.需要的头文件支持 #include <process.h> // for _beginthread() 需要的设置:Proj ...
- 使用Azure REST API创建虚拟机
Hollis Yao, Shihao Rong 使用REST API创建虚拟机之前,首先要确保Azure订阅中已经建好了"云服务"和"存储账号".如果没有的话 ...
- JAVA创建多线程
首先:线程与进程的区别是什么呢? 进程:正在运行的一个程序称之为一个进程,进程负责了内存空间的划分,从宏观的角度:windows是在同时执行多个程序 从微观的角度看,CPU是在快速的切换要执行的程序. ...
- Java多线程开发系列之二:如何创建多线程
前文已介绍过多线程的基本知识了,比如什么是多线程,什么又是进程,为什么要使用多线程等等. 在了解了软件开发中使用多线程的基本常识后,我们今天来聊聊如何简单的使用多线程. 在Java中创建多线程的方式有 ...
随机推荐
- 之江实验室: 如何基于 JuiceFS 为超异构算力集群构建存储层 ?
今天,高性能计算结合人工智能技术正在推动科研创新.例如通过破解水稻基因密码推动作物育种从"试验选优"向"计算选优"发展,在医药领域快速分析分子与蛋白之间的相互作 ...
- 【HMS Core】Health Kit注册订阅后,每种设备都会通过相同的回调地址上传数据?
[问题描述1] 注册订阅后,每种设备都会通过相同的回调地址上传数据? [解决方案] 一般和设备关系不大.订阅回调地址只有一个,当用户完成订阅,且用户数据在云端发生变化时,我们会向您提供的订阅地址发送 ...
- JavaScript 显示数据
JavaScript 显示数据 JavaScript 可以通过不同的方式来输出数据: 使用 window.alert() 弹出警告框. 使用 document.write() 方法将内容写到 HTML ...
- JavaWeb之Servlet详解(以及浏览器调用 Servlet 流程分析图)
Servlet 1.什么是Servlet Servlet(java 服务器小程序) 他是由服务器端调用和执行的(一句话:是Tomcat解析和执行) 他是用java语言编写的, 本质就是Java类 他是 ...
- 前端vue基于原生check增强单选多选插件
前端vue基于原生check增强单选多选插件, 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=12979 效果图如下: ...
- React后台管理系统06 路由
在src目录下新建2views文件夹,用来存放组件,这里我们新建2个路由组件Home About,如下所示: 创建好这两个路由组件之后,在src目录里面我们新建一个router路由文件夹,然后命名一个 ...
- 大模型微调技术LoRA与QLoRA
LoRA: Low-Rank Adaptation of Large Language Models 动机 大模型的参数量都在100B级别,由于算力的吃紧,在这个基础上进行所有参数的微调变得不可能.L ...
- 组合数学_第4章_Polya定理
第4章 Polya定理 4.1 群的概念 4.1.1 群的定义 给定一个集合\(G=\{a,b,c,\cdots\}\)和集合\(G\)上的二元运算"\(\cdot\)",并满足下 ...
- Servlet案例:发生错误org.springframework.dao.EmptyResultDataAccessException: Incorrect result size: expecte
20-Jun-2020 20:48:31.466 信息 [http-nio-8080-exec-7] com.alibaba.druid.pool.DruidDataSource.info {data ...
- Code Generate V2.0 代码生成器
Code Generate 代码生成器 系统首页 使用说明 系统默认会根据SQL生成字段信息 className.fieldList.classComment 如下所示: 建表语句 CREATE TA ...