首页 > 其他分享 >9.1 运用API创建多线程

9.1 运用API创建多线程

时间:2023-10-01 10:12:07浏览次数:29  
标签:函数 CreateThread beginthreadex int 临界 API 线程 多线程 9.1

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

标签:函数,CreateThread,beginthreadex,int,临界,API,线程,多线程,9.1
From: https://www.cnblogs.com/LyShark/p/17738611.html

相关文章

  • java: 用百度API读取增值税发票信息
     /***encoding:utf-8*版权所有2023涂聚文有限公司*许可信息查看:https://github.com/Baidu-AIP/java-sdk/blob/master/src/main/java/com/baidu/aip/http/AipRequest.java*描述:*#Author:geovindu,GeovinDu涂聚文.*#IDE:IntelliJIDEA202......
  • python提取论文图片波形数据:pyautogui键盘移动鼠标,跨模块全局变量使用,cv2局部放大窗口
    最近写了一个python提取论文图片波形数据的脚本,代码如下。涉及新知识点:pyautogui键盘移动鼠标,跨模块全局变量使用,cv2局部放大窗口,matplotlib图片在pyQT5lable显示,坐标变换,多线程同时使用。搜索相关关键字去对应代码区看注释就可以了。gui窗口:1#-*-coding:utf-8-*-2......
  • API商品数据接口调用
    一、API商品数据接口概述API商品数据接口是一种通用的数据交互方式,它允许不同系统之间进行数据传输和交互。API商品数据接口可以使用各种不同的协议和标准来实现,例如RESTfulAPI、SOAP、XML-RPC等。其中RESTfulAPI是最常用的一种,它基于HTTP协议和JSON格式进行数据传输,具有简单易用......
  • C++11 多线程< 一>、介绍
    1#include<iostream>2#include<thread>34voidfun1()5{6std::cout<<"fuck"<<std::endl;7}89intmain()//主线程10{11std::threadt1(fun1);//t1线程12//t1.join();//主线程和t1互不干扰,......
  • FastAPI学习-22.response 异常处理 HTTPException
    前言某些情况下,需要向客户端返回错误提示。这里所谓的客户端包括前端浏览器、其他应用程序、物联网设备等。需要向客户端返回错误提示的场景主要如下:客户端没有执行操作的权限客户端没有访问资源的权限客户端要访问的项目不存在等等...遇到这些情况时,通常要返回 4XX(40......
  • FastAPI学习-23.异常处理器 exception_handler
    前言通常我们可以通过raise抛出一个HTTPException异常,请求参数不合法会抛出RequestValidationError异常,这是最常见的2种异常。HTTPException异常向客户端返回HTTP错误响应,可以使用 raise触发 HTTPException。fromfastapiimportFastAPI,HTTPExceptionapp=Fa......
  • FastAPI学习-24.自定义异常处理器 exception_handler
    前言添加自定义处理器,要使用 Starlette的异常工具。安装自定义异常处理器假设要触发的自定义异常叫作 UnicornException。且需要FastAPI实现全局处理该异常。此时,可以用 @app.exception_handler() 添加自定义异常控制器:fromfastapiimportFastAPI,Requestfromfa......
  • FastAPI学习-25.response_model 定义响应模型
    你可以在任意的_路径操作_中使用 response_model 参数来声明用于响应的模型:@app.get()@app.post()@app.put()@app.delete()fromtypingimportAny,List,UnionfromfastapiimportFastAPIfrompydanticimportBaseModelapp=FastAPI()classItem(BaseModel)......
  • 爬虫记录~(多线程爬取图片)
    使用Requests+Re库方法多线程爬取亚马逊商城商品图片,以关键词“书包”搜索页面的商品的图片,爬取0-2页面商品图片。关键词:多线程爬虫程序、商城网站的遍历,链接的查找和访问。巩固搜索接口和翻页处理。importrequestsfromfake_useragentimportUserAgentimportrefrommulti......
  • How to get Postman API request code All In One
    HowtogetPostmanAPIrequestcodeAllInOnePostmancanconvertanAPIrequestintoacodesnippet,andyoucanchoosetheprogramminglanguageorframework.Youcanusethisgeneratedcodesnippetinyourfront-endapplications.https://learning.pos......