9.1 运用API创建多线程

在Windows平台下创建多线程有两种方式,读者可以使用CreateThread函数,或者使用beginthreadex函数均可,两者虽然都可以用于创建多线程环境,但还是存在一些差异的,首先CreateThread函数它是Win32 API的一部分,而_beginthreadexC/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;
}

总的来说,_beginthreadexCreateThread更加高级,封装了许多细节,使用起来更方便,特别是对于传递多个参数的情况下,可以更简单地传参。

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/922df2e6.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/91816.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第1篇 目标检测概述 —(4)目标检测评价指标

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。目标检测评价指标是用来衡量目标检测算法性能的指标&#xff0c;可以分为两类&#xff0c;包括框级别评价指标和像素级别评价指标。本节课就给大家重点介绍下目标检测中的相关评价指标及其含义&#xff0c;希望大家学习之后…

【中秋国庆不断更】HarmonyOS对通知类消息的管理与发布通知(上)

一、通知概述 通知简介 应用可以通过通知接口发送通知消息&#xff0c;终端用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应用。 通知常见的使用场景&#xff1a; 显示接收到的短消息、即时消息等。显示应用的推送消息&#xff0c;如广告、版本更新等。显示…

keepalived的vip实现nginx节点的主备

nginx wget http://nginx.org/download/nginx-1.18.0.tar.gz tar zxvf nginx-1.18.0.tar.gzyum install -y gcc gcc-c pcre pcre-devel zlib zlib-devel openssl openssl-devel libnl3-develcd nginx-1.18.0 mkdir -p /usr/local/nginx #需要使用https&#xff0c;在编译时启用…

【中秋国庆不断更】OpenHarmony多态样式stateStyles使用场景

Styles和Extend仅仅应用于静态页面的样式复用&#xff0c;stateStyles可以依据组件的内部状态的不同&#xff0c;快速设置不同样式。这就是我们本章要介绍的内容stateStyles&#xff08;又称为&#xff1a;多态样式&#xff09;。 概述 stateStyles是属性方法&#xff0c;可以根…

机器人中的数值优化(十九)—— SOCP锥规划应用:时间最优路径参数化(TOPP)

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

Vue3父子组件数据传递

getCurrentInstance方法 Vue2中&#xff0c;可以通过this来获取当前组件实例&#xff1b; Vue3中&#xff0c;在setup中无法通过this获取组件实例&#xff0c;console.log(this)打印出来的值是undefined。 在Vue3中&#xff0c;getCurrentInstance()可以用来获取当前组件实例…

SoloX:Android和iOS性能数据的实时采集工具

SoloX&#xff1a;Android和iOS性能数据的实时采集工具 github地址&#xff1a;https://github.com/smart-test-ti/SoloX 最新版本&#xff1a;V2.7.6 一、SoloX简介 SoloX是开源的Android/iOS性能数据的实时采集工具&#xff0c;目前主要功能特点&#xff1a; 无需ROOT/越狱…

新型信息基础设施IP追溯:保护隐私与网络安全的平衡

随着信息技术的飞速发展&#xff0c;新型信息基础设施在全球范围内日益普及&#xff0c;互联网已经成为我们社会和经济生活中不可或缺的一部分。然而&#xff0c;随着网络使用的增加&#xff0c;隐私和网络安全问题也引发了广泛关注。在这个背景下&#xff0c;IP&#xff08;In…

简单工厂模式~

我们以生产手机作为应用场景展开讲解&#xff01; 手机是一个抽象的概念&#xff0c;它包含很多的品牌&#xff0c;例如华为&#xff0c;苹果&#xff0c;小米等等&#xff0c;因此我们可将其抽象为一个接口&#xff0c;如下所示&#xff1a; public interface tel {void pro…

Docker的学习记录

Docker是一个被广泛使用的开源容器引擎&#xff0c;基于Go语言&#xff0c;遵从Apache2.0协议开源。 docker的三个概念&#xff1a;容器、镜像和仓库。 镜像&#xff08;Image&#xff09;&#xff1a;镜像是Docker中的一个模板。通过 Docker镜像 来创建 Docker容器&#xff…

angular 在vscode 下的hello world

Angulai 是google 公司开发的前端开发框架。Angular 使用 typescript 作为编程语言。typescript 是Javascript 的一个超集&#xff0c;提升了某些功能。本文介绍运行我的第一个angular 程序。 前面部分参考&#xff1a; Angular TypeScript Tutorial in Visual Studio Code 一…

详细介绍Redis RDB和AOF两种持久化方式

RDB持久化 RDB是Redis的一种快照持久化方式&#xff0c;它将内存中的数据集都写入磁盘&#xff0c;生成一个RDB文件&#xff0c;RDB文件是一个经过压缩的二进制文件&#xff08;通常叫做数据快照&#xff09;&#xff0c;可以用于备份、迁移和恢复数据。 RDB的优点是快速、紧…

Java之线程池的详细解析

1. 线程池 1.1 线程状态介绍 当线程被创建并启动以后&#xff0c;它既不是一启动就进入了执行状态&#xff0c;也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢&#xff1f;Java中的线程 状态被定义在了java.lang.Thread.Stat…

【并发编程】ThreadPoolExecutor任务提交与停止流程及底层实现【新手探索版】

文章目录 1. ThreadPoolExecutor任务提交2. 线程池状态[这部分是难点呀]2.1. addWorker添加worker线程2.2. 内部类Worker2.3. runWorker():执行任务2.4. getTask():获取任务2.5. processWorkerExit():worker线程退出 3.3. 关闭线程池3.3.1. shutdown方法3.3.2. shutdownNow方法…

密码技术 (2) - 公钥密码

一. 前言 公钥密码的秘钥分为加密秘钥和解密秘钥&#xff0c;加密秘钥成为公钥&#xff0c;解密秘钥成为私钥&#xff0c;公钥和私钥的成对存在的&#xff0c;一对公钥和私钥称为密钥对。发送消息时&#xff0c;发送者用公钥对消息进行加密&#xff0c;接收者用私钥进行解密。从…

golang官方限流器rate

包名&#xff1a;golang.org/x/time/rate 实现原理&#xff1a;令牌桶 package mainimport ("context""fmt""testing""time""golang.org/x/time/rate" )func TestLimiter(t *testing.T) {// 第一个参数代表速率&#xff1…

工厂与观察者模式

工厂模式介绍 通过一个加工厂&#xff0c;在这个工厂中添加对应材料&#xff0c;我们就可以得到想要的东西&#xff0c;在程序设计中&#xff0c;这种模式就叫做工厂模式&#xff0c;工厂生成出的产品就是某个类的实例&#xff0c;也就是对象。 关于工厂模式一共有三种&#…

小程序与uniapp如何进行传参

小程序和uniapp都可以通过以下方式进行传参&#xff1a; query参数传递&#xff1a;在url中添加query参数&#xff0c;如在小程序中通过wx.navigateTo或wx.redirectTo跳转页面时可传递query参数&#xff0c;在uniapp中通过uni.navigateTo或uni.redirectTo跳转页面时也可以传递q…

Qt::图层框架-图片图层-序列图层-QGraphicsPixmapItem

二维矢量动画智能制作软件开发合集 链接&#xff1a;软件开发技术分享及记录合集 个人开发二维矢量动画智能制作软件界面如下&#xff1a; 目录 一、图片序列图层原理 二、图片序列图层代码实现 三、图片序列图层软件测试视频 结束语 一、图片序列图层原理 本软件的11种…

C++11(列表初始化,声明,范围for)

目录 一、列表初始化 1、一般的列表初始化 2、容器的列表初始化 二、声明 1、 auto 2、decltype 3、nullptr 三、 范围for 一、列表初始化 1、一般的列表初始化 在C98中&#xff0c;标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。 int main() {…