【学习笔记】在 windows 下创建多线程 C++

【学习笔记】在 windows 下创建多线程 C++,第1张

目录
  • 先决条件
  • 传统的创建方式
    • 使用 CreateThread 函数
    • 实例
  • 更安全的方式
    • _beginthreadex
    • 实例
  • 终止线程
  • 补充
    • WaitForMultipleObjects 函数
      • 实例
  • 参考

先决条件

最好了解以下内容

  1. 了解内核对象
  2. 了解进程,线程的理论,基本知识
  3. 了解内存结构
  4. 了解堆栈
传统的创建方式 使用 CreateThread 函数
  1. 定义:该函数可以在进程的虚拟地址空间内创建线程
    【注】要在 A 进程中创建 B 进程里的线程,可以使用 CreateRemoteThread 函数
  2. 语法
HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  [in]            SIZE_T                  dwStackSize,
  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,
  [in, optional]  __drv_aliasesMem LPVOID lpParameter,
  [in]            DWORD                   dwCreationFlags,
  [out, optional] LPDWORD                 lpThreadId
);

参数说明

  • lpThreadAttributes:线程的句柄是否可以被子进程继承,一般选择填 NULL,表示不可继承
  • dwStackSize:初始化栈的大小,其单位是字节,如果参数为 0,表示使用默认的大小
  • lpStartAddress:要执行的函数的地址
  • lpParameter:要传入该函数的参数,这是一个指针结构
  • dwCreationFlags:“创建标志位”,如果参数为 0 线程创建后立即执行,参数为 CREATE_SUSPENDED 表示线程创建后进入挂起状态,直到 ResumeThread 函数调用
  • lpThreadId:接收线程标识符,如果为 NULL,表示不接收。个人觉得这个就是线程的 id 号
  • 返回值:如果函数成功执行则返回指向这个新创建的线程的句柄

【注】in 表示输入参数,out 表示输出参数,optional 表示参数可选
【注】即使 lpStartAddress 指向数据、代码或不可访问的区域,CreateThread 也可能会执行成功
【注】如果多线程要调用 CRT,则应该使用 _beginthreadex 函数来创建线程,同时它具有更高的安全性

实例
#include 
#include 

#define HANDLENUM 5

// function declaration
DWORD WINAPI MyThreadFunction(LPVOID lpParam);

// pass paramter
typedef struct PassValue{
	int val1;
	int val2;
}PASSVALUE, *PPASSVALUE;

void m_CreateThread()
{
	HANDLE aHandleArray[HANDLENUM];
	DWORD adwThreadID[HANDLENUM]; // receive thread descriptor

	// fill paramter
	PASSVALUE pv{
		1,
		2
	};

	// create threads
	for (int i = 0; i < HANDLENUM; i++)
	{
		aHandleArray[i] = CreateThread(NULL, 0, MyThreadFunction, &pv, 0, &adwThreadID[i]);
	}

	// omit error handling ...

	// wait for all thread execution completed
	WaitForMultipleObjects(HANDLENUM, aHandleArray, TRUE, INFINITE);

	printf("所有线程执行完毕\n");

	// close handle
	for (int i = 0; i < HANDLENUM; i++)
	{
		CloseHandle(aHandleArray[i]);
	}
}

DWORD WINAPI MyThreadFunction(LPVOID lpParam)
{
	PASSVALUE *pv = (PASSVALUE *)lpParam;
	printf("thread id is %d, pass paramter one is: %d, pass paramter two is: %d\n", GetCurrentThreadId(), pv->val1, pv->val2);

	return 0;
}

执行结果

thread id is 23408, pass paramter one is: 1, pass paramter two is: 2
thread id is 24404, pass paramter one is: 1, pass paramter two is: 2
thread id is 13612, pass paramter one is: 1, pass paramter two is: 2
thread id is 33796, pass paramter one is: 1, pass paramter two is: 2
thread id is 8032, pass paramter one is: 1, pass paramter two is: 2
所有线程执行完毕
更安全的方式 _beginthreadex

_beginthread 与其属于同一类方式,这里不单独说明

  1. 定义:创建线程的函数
  2. _beginthreadexCreateThread 的区别:_beginthreadex 相较来说更加的安全,不会产生内存泄漏,在 _beginthreadex 的源码中,实际是对 CreateThread 进行了封装,所以你可以看到它们的参数几乎是通用的
  3. 语法
uintptr_t _beginthreadex( // NATIVE CODE
   void *security,
   unsigned stack_size,
   unsigned ( __stdcall *start_address )( void * ),
   void *arglist,
   unsigned initflag,
   unsigned *thrdaddr
);

参数解释:

  • security:如果该参数为 NULL,则表示不能继承
  • stack_size:为 0 时,使用默认的栈大小
  • start_address:执行的函数,注意这里的函数类型和 CreateThread 中的就不一样了,要用它的来定义
  • arglist:传入要执行的函数的参数的指针
  • initflag:为 0 表示线程立即执行
  • thrdaddr:线程标识符
  • 返回值:线程句柄
实例

该部分代码仅对 CreateThread 中的实例做了很小的修改即可运行

#include 
#include 
#include     /* _beginthread, _endthread */

#define HANDLENUM 5

unsigned __stdcall MyThreadFunction(void *lpParam);

// pass paramter
typedef struct PassValue {
	int val1;
	int val2;
}PASSVALUE, *PPASSVALUE;

void m_beginthreadex()
{
	HANDLE aHandleArray[HANDLENUM];
	unsigned int adwThreadID[HANDLENUM]; // receive thread descriptor

	// fill paramter
	PASSVALUE pv{
		1,
		2
	};

	// create threads
	for (int i = 0; i < HANDLENUM; i++)
	{
		aHandleArray[i] = (HANDLE)_beginthreadex(NULL, 0, MyThreadFunction, &pv, 0, &adwThreadID[i]);
	}

	// omit error handling ...

	// wait for all thread execution completed
	WaitForMultipleObjects(HANDLENUM, aHandleArray, TRUE, INFINITE);

	printf("所有线程执行完毕\n");

	// close handle
	for (int i = 0; i < HANDLENUM; i++)
	{
		CloseHandle(aHandleArray[i]);
	}
}

unsigned __stdcall MyThreadFunction(void *lpParam)
{
	PASSVALUE *pv = (PASSVALUE *)lpParam;
	printf("thread id is %d, pass paramter one is: %d, pass paramter two is: %d\n", GetCurrentThreadId(), pv->val1, pv->val2);

	return 0;
}

执行结果

thread id is 1920, pass paramter one is: 1, pass paramter two is: 2
thread id is 11760, pass paramter one is: 1, pass paramter two is: 2
thread id is 2896, pass paramter one is: 1, pass paramter two is: 2
thread id is 6556, pass paramter one is: 1, pass paramter two is: 2
thread id is 29496, pass paramter one is: 1, pass paramter two is: 2
所有线程执行完毕
终止线程

有四种终止运行线程的方式

  • 线程函数返回,也就是让线程正常执行完毕(推荐)
  • 线程通过调用 ExitThread 或者 _endthreadex 函数杀死自己,也就是与创建线程对应的函数
  • 其他线程(不一定是同一个进程的)调用 TerminateThread 杀死另一个线程
  • 进程终止
补充 WaitForMultipleObjects 函数
  1. 定义:该函数用于等待执行完所有线程才执行接下来的代码语句,MSDN 中的描述语句是,等待指定对象变成有信号(signaled)的状态。或者,函数第二个参数用于设置超时
  2. 语法
DWORD WaitForMultipleObjects(
  [in] DWORD        nCount,
  [in] const HANDLE *lpHandles,
  [in] BOOL         bWaitAll,
  [in] DWORD        dwMilliseconds
);
  1. nCount:有多少句柄需要等待。结合 CreateThread 中的实例,其实就是要等待的线程的数量
  2. lpHandles:线程句柄
  3. bWaitAll:为 TRUE 表示等待所有线程执行完毕。为 FALSE 表示任何一个线程执行完成就返回
  4. dwMilliseconds:超时设定,如果设置为某个大于 0 的数值,则在该数值到达后,而线程又没有执行完,也依旧继续执行主线程中的剩余代码。若设置为 INFINITE 表示无限等待。
实例

我们将 CreateThread 小节中实例的代码修改如下部分,以演示 bWaitAll 设置为 FLASE 的效果

  1. 将 HANDLENUM 设置为 50,方便我们更容易观察结果
  2. bWaitAll 设置为 FLASE 表示只要有一个线程触发信号则 WaitForMultipleObjects 函数执行完成

bWaitAll 设置为 TRUE 时,“所有线程执行完毕” 这句话始终出现在输出语句的结尾,而当 bWaitAll 设置为 FLASE 其效果如下

可以看到,该语句将不会在最后出现。
【注】当如果你只用等待一个信号的时候,可以使用 WaitForSingleObject

参考
  1. 《Windows 核心编程》
  2. CreateThread function
  3. Creating Threads
  4. WaitForMultipleObjects function
  5. _beginthread, _beginthreadex
  6. windows 多线程: CreateThread、_beginthread、_beginthreadex、AfxBeginThread 的区别

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/2889420.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-14
下一篇 2022-09-14

发表评论

登录后才能评论

评论列表(0条)

保存