多线程一之Thread创建、运行、结束

背景

面对一个任务并行的多线程问题,我们首先要将总任务分为多个子任务,一个子任务就可以放入一个线程中执行,这时我们需要编写多个线程,每个线程负责独立的任务;

而面对一个数据并行的多线程问题,我们只有一个任务,是将数据分为多个独立的子数据,每个数据都执行同样的任务,这时我们只需要编写一个线程,通过管理线程的输入来完成多个子数据的并行任务。

所以我们把线程想象成任务即可,这个任务要完成多少工作无关紧要,只要多个任务是独立的,或者同一个任务有多个独立的输入。

线程函数

CreateThread

在Windows系统中,我们可以使用CreateThread函数:

HANDLE WINAPI CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,
    _In_ DWORD dwCreationFlags,
    _Out_opt_ LPDWORD lpThreadId
);

函数说明:

  • 第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
  • 第二个参数表示线程栈空间大小。传入0表示使用默认大小(1MB)。
  • 第三个参数表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
  • 第四个参数是传给线程函数的参数。
  • 第五个参数指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。
  • 第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程ID号。

函数返回值:成功返回新线程的句柄,失败返回NULL。

CreateThread对应的关闭线程函数为为 ExitThread

_beginthread

实际上,CreateThread函数并不被推荐使用, _beginthread要比CreateThread安全,它是对CreateThread函数的封装,并针对CreateThread在调用CRT库时的内存泄漏问题进行了处理,所以比CreateThread更加安全。_beginthread定义如下:

uintptr_t _beginthread(
	void( *start_address )( void * ),
	unsigned stack_size,
	void *arglist
);

参数说明:

  • start_address:新线程的起始地址 ,指向新线程调用的函数的起始地址,为函数名。函数的声明方式很简单:

    void 函数名(LPVOID lpParam)
    
  • stack_size:新线程的堆栈大小,一般为0

  • arglist:传递给线程的参数列表,无参数时为NULL

_beginthread对应的关闭线程函数为为_endthread

_beginthreadex

_beginthread函数非常简单,易上手,但参数太少也有缺点,和CreateThread比起来似乎可控制性不强。我们还可以使用另一个函数_beginthreadex

unsigned long _beginthreadex(
    void *security,
    unsigned stack_size,
    unsigned(_stdcall *start_address)(void *),
    void *argilist,
    unsigned initflag,
    unsigned *threaddr
);

参数说明:

  • security:安全属性, 为NULL时表示默认安全性
  • stack_size:线程的堆栈大小, 一般默认为0
  • start_address:所要启动的线程函数
  • argilist:线程函数的参数, 是一个void*类型, 传递多个参数时用结构体
  • flag:新线程的初始状态,0表示立即执行,CREATE_SUSPEND表示创建之后挂起
  • threaddr:线程ID地址

返回值:成功返回新线程句柄, 失败返回0

可以看到,_beginthreadex就像_beginthread的扩展体,增加了三个参数,是可控制性更好一些。两个函数的不同点如下:

  • 多3个参数:intiflag,security和threadaddr。

  • 线程函数的声明方式不同。_beginthreadex()的线程函数必须使用_stdcall修饰符,且必须返回一个unsigned int型的退出码。

    unsigned __stdcall 函数名(void* param)
    
  • _beginthreadex()在创建线程失败时返回0,而_beginthread()在创建线程失败时返回-1。这一点是在检查返回结果时必须注意的。

  • 如果是调用_beginthread创建线程,则相应地调用_endthread结束线程时,系统会自动关闭线程句柄;而调用_beginthreadex创建线程,相应地调用_endthreadx结束线程时,系统不能自动关闭线程句柄。因此调用_beginthreadex创建线程还需程序员自己关闭线程句柄,以清除线程的地址空间

WaitForSingleObject

函数功能:等待函数 – 使线程进入等待状态,直到指定的内核对象被触发。

函数原型:

DWORD WINAPI WaitForSingleObject(
  HANDLE hHandle,
  DWORD dwMilliseconds
);

参数说明:

  • 第一个参数为要等待的内核对象。
  • 第二个参数为最长等待的时间,以毫秒为单位,如传入5000就表示5秒,传入0就立即返回,传入INFINITE表示无限等待。

因为线程的句柄在线程运行时是未触发的,线程结束运行,句柄处于触发状态。所以可以用WaitForSingleObject()来等待一个线程结束运行。

函数返回值:

在指定的时间内对象被触发,函数返回WAIT_OBJECT_0。超过最长等待时间对象仍未被触发返回WAIT_TIMEOUT。传入参数有错误将返回WAIT_FAILED

实例

//子线程报数
#include <stdio.h>
#include <process.h>
#include <windows.h>
int g_nCount;
//子线程函数
unsigned int __stdcall ThreadFun(PVOID pM)
{
	g_nCount++;
	printf("线程ID号为%4d的子线程报数%d\n", GetCurrentThreadId(), g_nCount);
	return 0;
}
//主函数,所谓主函数其实就是主线程执行的函数。
int main()
{
	printf("     子线程报数 \n");
	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
	
	const int THREAD_NUM = 10;
	HANDLE handle[THREAD_NUM];
 
	g_nCount = 0;
	for (int i = 0; i < THREAD_NUM; i++)
		handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
	WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
	return 0;
}

在这里插入图片描述

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值