背景
面对一个任务并行的多线程问题,我们首先要将总任务分为多个子任务,一个子任务就可以放入一个线程中执行,这时我们需要编写多个线程,每个线程负责独立的任务;
而面对一个数据并行的多线程问题,我们只有一个任务,是将数据分为多个独立的子数据,每个数据都执行同样的任务,这时我们只需要编写一个线程,通过管理线程的输入来完成多个子数据的并行任务。
所以我们把线程想象成任务即可,这个任务要完成多少工作无关紧要,只要多个任务是独立的,或者同一个任务有多个独立的输入。
线程函数
CreateThread
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
_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
_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
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;
}