9.1 运用API创建多线程

运用,api,创建,多线程 · 浏览次数 : 20

小编点评

全局变量代码,定义了一个名为 g_nNum 的整数,用来存放子线程的个数。 **创建线程** * 使用 _beginthreadex() 创建线程,并设置线程的入口函数和参数。 * 获取线程的 ID。 **使用线程临界区** * 使用 EnterCriticalSection() 和 LeaveCriticalSection() 函数来锁定和释放线程临界区。 * 使用 CriticalSection 的变量来控制线程访问临界区的锁。 **测试** * 使用 WaitMultipleObjects() 函数等待多个线程执行完毕。 * 使用 DeleteCriticalSection() 函数释放子线程的临界区。

正文

在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 许可协议。转载请注明出处!

与9.1 运用API创建多线程相似的内容:

9.1 运用API创建多线程

在Windows平台下创建多线程有两种方式,读者可以使用`CreateThread`函数,或者使用`beginthreadex`函数均可,两者虽然都可以用于创建多线程环境,但还是存在一些差异的,首先`CreateThread`函数它是`Win32 API`的一部分,而`_beginthreadex`...

13.1 使用DirectX9绘图引擎

DirectX 9 是由微软开发的一组多媒体应用程序接口API,用于创建和运行基于Windows平台的多媒体应用程序,尤其是游戏。它是DirectX系列中的一个版本,于2002年发布,是DirectX系列中的一个重要版本,DirectX 9在其发布时引入了许多新的功能和性能优化,成为当时PC游戏开发...

使用 TensorRT C++ API 调用GPU加速部署 YOLOv10 实现 500FPS 推理速度——快到飞起!!

NVIDIA ® TensorRT ™ 是一款用于高性能深度学习推理的 SDK,包含深度学习推理优化器和运行时,可为推理应用程序提供低延迟和高吞吐量。YOLOv10是清华大学研究人员近期提出的一种实时目标检测方法,通过消除NMS、优化模型架构和引入创新模块等策略,在保持高精度的同时显著降低了计算开销...

9.2 运用API实现线程同步

Windows 线程同步是指多个线程一同访问共享资源时,为了避免资源的并发访问导致数据的不一致或程序崩溃等问题,需要对线程的访问进行协同和控制,以保证程序的正确性和稳定性。Windows提供了多种线程同步机制,以适应不同的并发编程场景。以上同步机制各有优缺点和适用场景,开发者应根据具体应用场景进行选...

本计划在 .NET 8 中推出的 WASI 推迟到 .NET 9

本计划在 .NET 8 中推出的 WASI 已推迟到 .NET 9,请参阅 Github 上的 WASI 跟踪问题。 在.NET 8 Preview 4 开始支持生成与 WASI 兼容的 .wasm 文件,使用独立的 WebAssembly 运行时 Wasmtime CLI[1] 运行该文件。去年的

函数的作用域和匿名函数、闭包、回调

一、匿名函数: 1、filter函数,可以过滤出列表中大于3的数据,但是使用都需要提前定义一个函数,有没有更加简便的方式呢? def f(o): # 过滤器 if o>3: print(o) list(filter(f,[3,1,5,9,7,10])) 运行截图: 2、匿名函数(lambda后面是空

华为运动健康服务Health Kit 6.9.0版本新增功能揭秘!

华为运动健康服务(HUAWEI Health Kit)6.9.0版本新鲜出炉啦! 一文了解新增功能,快来一起加入Health Kit生态大家庭! 一、更丰富:睡眠呼吸记录健康数据开放 呼吸机是用于为患者提供或增加肺通气的常用医疗器械,目前越来越多的家用呼吸机被用于缓解人们在日常睡眠过程中的打鼾、睡眠

DevOps|破除壁垒,重塑协作-业务闭环释放产研运协作巨大效能

- 会议太多了,员工开会效率降低了50%! 上篇文章《研发效能组织架构:职能独立vs业务闭环》介绍了职能独立型组织架构和业务闭环型组织架构的特点,优劣势。也许有的小伙伴可能对这两种组织架构没有深刻的体会,而本文就是想通过数据说话,想仅仅通过计算这两种组织架构下开会时间这一项,让大家知晓职能型组织架构

C#实现多线程的几种方式

前言 多线程是C#中一个重要的概念,多线程指的是在同一进程中同时运行多个线程的机制。多线程适用于需要提高系统并发性、吞吐量和响应速度的场景,可以充分利用多核处理器和系统资源,提高应用程序的性能和效率。 多线程常用场景 CPU 密集型任务. I/O 密集型任务. 并发请求处理. 大数据处理等. 什么是

5.9 汇编语言:浮点数操作指令

浮点运算单元是从80486处理器开始才被集成到CPU中的,该运算单元被称为FPU浮点运算模块,FPU不使用CPU中的通用寄存器,其有自己的一套寄存器,被称为浮点数寄存器栈,FPU将浮点数从内存中加载到寄存器栈中,完成计算后在回写到内存中。FPU有8个可独立寻址的80位寄存器,分别名为`R0-R7`他们以堆栈的形式组织在一起,栈顶由FPU状态字中的一个名为TOP的域组成,对寄存器的引用都是相对于栈顶