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

  • Post author:
  • Post category:其他




背景

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

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

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



线程函数




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;
}

在这里插入图片描述



版权声明:本文为weixin_45004203原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。