一、引言
接下来,笔者主要谈下Windows平台下多线程的用法。首先,要强调一点,Windows平台的线程linux中的线程不同,它会伴着主函数的结束而销毁。Windows平台下可用的创建多线程的API共有四个,分别是:(1) CreateThread()
CreateThread是Windows的API函数,提供操作系统级别的创建线程的操作,且仅限于工作者线程。在MFC中不要使用。(2) _beginthreadex()
_beginthreadex 是微软C/C++运行时库函数,线程安全标准的C函数,它针对C的运行时库做了一些初始化的工作,以保证C运行时库能够正常使用。然后,内部调用CreateThread创建线程。(3) _beginthread()
_beginthread相对_beginthreadex来说参数比较少,如无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程的ID值。特别是调用beginthreadex时,它会让返回的线程句柄值失效,用来防止用户直接访问内核对象(4) AfxBeginThread()
AfxBeginThread是MFC中线程创建的MFC函数,首先创建了相应的CWinThread对象,然后调用CWinThread::CreateThread,在CWinThread::CreateThread中,完成了对线程对象的初始化工作,然后,调用_beginthreadex(AfxBeginThread相比较更为安全)创建线程。它简化了操作或让线程能够响应消息,即可用于界面线程,也可以用于工作者线程,但要注意不要在一个MFC程序中使用_beginthreadex()或CreateThread()。
1、CreateThread函数是用来创建线程的Windows函数。不过,如果你正在编写C/C++代码,不应该调用 CreateThread。相反,应该使用Visual C++运行时库函数_beginthreadex。如果不使用Microsoft的Visual C++编译器,你的编译器供应商有它自己的CreateThred替代函数。
2、 线程的句柄不同于线程的ID,句柄是在同一个进程中区分内核对象的标志,同一个进程中一个内核对象的句柄值是唯一的,但是不同进程之间句柄值可能相等;而ID在跨进程中也是保持唯一的。
二、CreateThread
函数的原型如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpsa,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE pfnThreadProc,
void* pvParam,
DWORD dwCreationFlags,
DWORD* pdwThreadId
)
sa —— 线程的安全属性,默认情况下设置为NULL。
dwStackSize —— 为线程分配的调用堆栈的大小,传递0时会采用默认的大小。
pfnThreadProc —— 线程函数,默认声明格式为unsigned WINAPI ThreadFunc(void* arg);
pvParam —— 线程函数的参数指针,void*类型。
dwCreationFlags —— 创建后线程的状态,0表示创建后立即执行,使用CREATE_SUSPENDED宏,表示创建后被立即挂起,直到使用ResumeThread可以再次激活线程。pdwThreadId —— 保存线程ID的指针,如果不需要获取线程ID,填写NULL即可。
#include "stdafx.h"
#include "Windows.h"
#include "stdio.h"
unsigned WINAPI ThreadFunc1(void* pargs)
{
while (1)
{
puts("ThreadFunc1 Run ...");
Sleep(1000);
}
return 0;
}
unsigned WINAPI ThreadFunc2(void* pargs)
{
while(1)
{
puts("ThreadFunc2 Run ...");
Sleep(1000);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
//创ä¡ä建¡§一°?个?线?程¨¬初?始º?状Á¡ä态¬?为a运?行D
HANDLE hThread1 = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc1,NULL,0,NULL);
//创ä¡ä建¡§另¢¨ª一°?个?线?程¨¬,ê?初?始º?状Á¡ä态¬?为a挂¨°起e
HANDLE hThread2 = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc2,NULL,CREATE_SUSPENDED,NULL);
Sleep(5000);
puts("ResumeThread ...");
ResumeThread(hThread2);
Sleep(10000);
return 0;
}
第5行和第14行,分别创建了两个线程函数ThreadFunc1和ThreadFunc2,每个线程函数中都每个1秒钟循环打印各自的字符串。
第27行和29行,分别创建了两个线程,hThread1创建时激活,hThread2创建时被挂起,知道sleep 5 秒后被ResumeThread函数唤醒。
我们期望的最终的效果就是ThreadFunc1先输出5个字符串,5秒之后,ThreadFunc2和ThreadFunc1各自输出10个字符串,以下是输出结果:
三、_beginthreadex
uintptr_t _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( __stdcall *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
_beginthreadex()的参数和CreateThread的参数几乎一模一样,这里就不再重复解释,验证_beginthreadex时,我们只需要将原来的代码中CreateThread换成
_beginthreadex,再微调一下代码:
#include "stdafx.h"
#include "Windows.h"
#include "stdio.h"
#include "process.h"
unsigned WINAPI ThreadFunc1(void* pargs)
{
while (1)
{
puts("ThreadFunc1 Run ...");
Sleep(1000);
}
return 0;
}
unsigned WINAPI ThreadFunc2(void* pargs)
{
while(1)
{
puts("ThreadFunc2 Run ...");
Sleep(1000);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
//创ä¡ä建¡§一°?个?线?程¨¬初?始º?状Á¡ä态¬?为a运?行D
HANDLE hThread1 = (HANDLE)_beginthreadex(NULL,0,ThreadFunc1,NULL,0,NULL);
//创ä¡ä建¡§另¢¨ª一°?个?线?程¨¬,ê?初?始º?状Á¡ä态¬?为a挂¨°起e
HANDLE hThread2 = (HANDLE)_beginthreadex(NULL,0,ThreadFunc2,NULL,CREATE_SUSPENDED,NULL);
Sleep(5000);
puts("ResumeThread ...");
ResumeThread(hThread2);
Sleep(10000);
return 0;
}
从上面的代码可以看出,将CreateThread换成_beginthreadex,我们主要修改的地方有三处:
1、 增加了头文件processs.h
2、 _beginthreadex的返回值需要强转成HANDLE。3、 传入的线程函数不再需要LPTHREAD_START_ROUTINE进行强转。
四、beginthread
beginthread的函数原型如下:
uintptr_t _beginthread(
void( __cdecl *start_address )( void * ),
unsigned stack_size,
void *arglist
);
可以看出来,相比较_beginthreadex而言,它的参数要少得多。只有线程函数的地址start_address,分配堆栈的大小stack_size,以及线程传递的参数arglist。我们使用
beginthread的例子很简单,就是创建一个线程函数打印主函数传递过来的字符串:
#include "stdafx.h"
#include "Windows.h"
#include "stdio.h"
#include "process.h"
void ThreadFunc(void* pargs)
{
char* str=(char*)pargs;
puts(str);
}
int _tmain(int argc, _TCHAR* argv[])
{
char* str="我是主线程传进来的字符串”;
HANDLE hThread1 = (HANDLE)_beginthread(ThreadFunc,0,(void*)str);
Sleep(5000);
return 0;
}
运行结果:
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
Git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL42