Windows下的进程和Linux下的进程是不一样的,它比较懒惰,从来不执行任何东西,它只是为线程提供执行环境,然后由线程负责执行包含在进程的地址空间中的代码。当创建一个进程的时候,操作系统会自动创建这个进程的第一个线程,成为主线程。线程由两部分组成:一是线程的内核对象。操作系统用它来对线程实施管理,内核对象也是系统用来存放线程统计信息的地方。二是线程栈。线程栈用于维护线程在执行代码时所需的所有函数参数和局部变量。线程可以访问所在进程的内核对象的所有句柄、所有内存和这个进程中的其他线程的堆栈。
在Windows下你可以调用Win SDK的API来创建一个线程,也可以用使用C Run-Time Library中的函数来创建,还可以使用MFC封装的相关线程函数来得到你想要的线程。最近正好阅读了相关方面的书籍,正好做下总结。
一 WinAPI下的线程
1 创建线程
HANDLE
WINAPI
CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in_opt LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out_opt LPDWORD lpThreadId
);
- lpThreadAttributes
指向LPSECURITY_ATTRIBUTES结构体的指针,默认设为NULL,让线程使用默认的安全性。如果希望所有子线程能够继承线程对象的句柄,则必须设定LPSECURITY_ATTRIBUTES结构体,将它的bInHeriHandle初始化未TRUE。
- dwStackSize
设置初始栈的大小,默认设为0。
- lpStartAddress
指向LPTHREAD_START_ROUTINE指向的函数指针,即为新线程的起始地址。该函数的名称任意,但是函数类型必须遵照下面声明的形式:
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
LPVOID lpThreadParameter
);
- lpParameter
新线程调用函数的命令行参数。
- dwCreationFlags
用于控制线程创建的附加标记。可以使两个值之一:CRETE_SUSPENDED或0。如果是SUSPENDED,那么线程创建后将处于暂停状态。如果是0,线程在创建后立即运行。
- lpThreadId
指向一个变量,用来接受线程的ID。如果对线程ID不感兴趣可以直接设为NULL。
注意:CreateThread()函数传回的值有两个,第一个值是返回值HANDLE,这个值是线程的HANDLE,是线程所在的进程中的局部变量,在不同的进程中是不唯一的,所以你不能直接跨进程的把一个线程的HANDLE传给另外一个进程中的线程让其使用。第二个值是由lpThreadId传回的线程ID,此值是一个全局变量,可以独一无二的表示系统中任意进程中的某个线程。比如调用AttachThreadInput()或PostThreadMessage()就需要用线程ID,而不能用线程的HANDLE。
2 关闭线程句柄
当完成工作后,应该带哦用CloseHandle释放核心对象。
BOOL
WINAPI
CloseHandle(
__in HANDLE hObject
);
如果成功则传回TRUE,失败则传回FALSE。当创建一个线程后立刻调用CloseHandle()函数并不会终止新创建的线程,只是表示在主线程中新创建的线程的引用不感兴趣,因此将它关闭。当关闭该线程的句柄时,系统会递减该线程内核对象的使用计数。线程被创建的时候,线程内核对象的默认引用计数是2,当创建的这个新线程执行完毕后,系统也会递减该线程内核对象的引用计数。当使用计数为0时,系统会释放该线程内核对象。如果没有关闭线程句柄,系统就会一直保持对线程内核对象的引用,这样即使线程执行完毕,它的引用计数仍然不会为哦,这样该线程内核对象也就不会被释放,只有等到进程终止时,系统才会清理这些残留的对象。因此在程序中,当不再需要线程的句柄时,应将其关闭。
3 线程结束代码
BOOL
WINAPI
GetExitCodeThread(
__in HANDLE hThread,
__out LPDWORD lpExitCode
);
如果成功,GetExitCodeThread()传回TRUE,否则传回FALSE。GetExitCodeThread()将传回线程函数的返回值,但是当线程还在进行尚未结束代码时,它会传回TRUE表示成功,此时lpExitCode指向的内存区域存放的是STILL_ACTIVE。
4 结束线程
当线程函数结束的时候,线程也就结束了,但是还可以用更暴力的手段让线程结束,就是调用线程结束函数ExitThread():
VOID
WINAPI
ExitThread(
__in DWORD dwExitCode
);
此函数可以在任何时候被调用而绝不会返回,任何在它之后的代码,都不会被调用。
还可以调用函数TerminateThread函数来杀死一个线程,不同于ExitThread总是杀死主调线程,TerminateThread可以杀死任何线程。
VOID
WINAPI
ExitThread(
HANDLE hThread,
DWORD dwExitCode
);
示例程序1:创建5个线程,此5个线程的调用函数打印出当前线程正在运行,然后返回。同时,我们另外创立一个监控监控线程,来监控这个5个线程的状态。
#include <windows.h> #include <process.h> #include <iostream> #include <stdio.h> #include <string.h> #include <tchar.h> #define THREAD_NUM 5 DWORD WINAPI ThreadFunc(LPVOID); DWORD WINAPI MonitorFunc(LPVOID); void ExitFunc(void); int main( ) { HANDLE hThread[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; ++i) { hThread[i] = CreateThread(NULL, 0, ThreadFunc,(LPVOID)i, 0, NULL); } HANDLE monitorThread; monitorThread = CreateThread(NULL, 0, MonitorFunc, (LPVOID)hThread, 0, NULL); if (WaitForSingleObject(monitorThread, INFINITE)) CloseHandle(monitorThread); for (int i = 0; i < THREAD_NUM; ++i) { if (WaitForSingleObject(hThread[i], INFINITE)) { CloseHandle(hThread[i]); } } cout<<"所有线程已经运行结束!"<<endl; system("pause"); return EXIT_SUCCESS; } DWORD WINAPI ThreadFunc(LPVOID lpPara) { cout<<"线程"<< (int)(lpPara)<<"正在运行"<<endl; Sleep(10); return 0; } DWORD WINAPI MonitorFunc(LPVOID hThread) { DWORD exitCoed; int exitThreadCount = 0; int threadStatus[THREAD_NUM]; memset(threadStatus, 0, THREAD_NUM); while(TRUE) { for (int i = 0; i < THREAD_NUM; ++i) { GetExitCodeThread( ((HANDLE*)hThread)[i], &exitCoed); if (exitCoed == STILL_ACTIVE) cout<<"根据ExitCode判断:线程"<<i<<"仍然在运行!"<<endl; else { cout<<"根据ExitCode判断:线程"<<i<<"已经结束运行"<<endl; threadStatus[i] = 1; } } exitThreadCount = 0; for (int i = 0; i < THREAD_NUM; ++i) exitThreadCount += threadStatus[i]; if (exitThreadCount == THREAD_NUM) break; } return 0; }
二 C Run-time Library 下的线程
在《Win32 多线程程序设计》一书中说道:“在微软的Programing Techniques说明文件中有一句看似悲惨的警告”:
警告:如果你在一个与LIBCMT.LIB链接的程序中调用C runtime函数,你的线程就必须以_beginthread()启动之,不要使用Win32的ExitThread()和ExitThread()。这
这句警告是说_beginthread有一个冲突状态,不可完全信赖它。所以在C Runtime Library中应该用_beiginthreadex()和_endthreadex()。主要原因是C Runtime libray是上个世纪70年代产生出来的,那个时候多任务还是新奇概念,所以更不知道多线程是什么东东了。这导致C Runtime libray中的使用的数个全局变量和静态变量可能在多线程程序中彼此引起冲突,比如errno。还有一点,_beginthread产生出来的线程所做的第一件事情就是关闭自己的handle。所以现在Visual C++提供了两个版本的C Runtime libray,一个版本供单线程程序使用,一个供多线程程序使用。第三部分介绍的MFC程序必须使用多线程的C Runtime libray,否则在链接时会出现错误。
1 线程的创建
uintptr_t __cdecl _beginthreadex(_In_opt_ void * _Security, _In_ unsigned _StackSize, _In_ unsigned (__stdcall * _StartAddress) (void *), _In_opt_ void * _ArgList, _In_ unsigned _InitFlag, _In_opt_ unsigned * _ThrdAddr);
参数说明:
- _Security
相当于CreateThread()中的安全属性参数,默认设置为NULL。对应Win32数据类型是LPSECURITY_ATTRIBUTES。
- _StackSize
新线程的堆栈大小,单位是字节(byte)。对应Win32数据类型是DWORD
- _StartAddress
线程启动函数。
- _ArgList
新线程将接收到一个指针,这个指针指示单纯的被传过去,运行库并没有对它做拷贝操作。
- _InitFlag
启动时的状态标记。
- _ThrdAddr
新线程的ID通过此参数传回。
_beginthreadex函数的返回值为handle,这个值必须被强制转换为Win32的HANDLE之后才能被使用。如果函数失败,则传回0,而其原因被设置在errno和doserrno全局变量中。
2 关闭线程句柄
在C Runtime Libray下的_beiginthreadex()创建线程内部调用的就是WinAPI的CreateThread()函数。这也是很让人蛋疼的地方,微软想让C Runtime Libray跨平台,但是却使用的WinAPI的函数,想法是好的,只是没有去实现。正是这样子,所以_beiginthreadex()的返回值handle就是WinAPI中的HANDLE,只是需要强制转换下才能使用。所以,在C Runtime Libray下关闭一个线程的句柄同样是调用函数CloseHandle()。
3 结束一个线程
在C Runtime Libray下于ExitThread()相对应的函数是_endthreadex():
void __cdecl _endthreadex(_In_ unsigned _Retval);
和ExitThread()函数一样,_endthreadex()可以被线程在任意时间使用,它需要表示一个“线程返回代码”的参数。但是绝对不要再一个以_beiginthreadex()启动的线程中调用ExitThrad(),因为这样C Runtime Libray就没有机会释放该线程的资源了。
为了适当的清除C Runtime Libray中的结构,对于以_beiginthreadex()来产生新线程的程序,应该使用下面两种方法来结束程序:
- 调用C Runtime Libray中的exit()函数。
- 从main()返回系统。
任何一种情况下,runtime libray都会自动进行清理操作,组后调用ExitProcess()。使用任一种技术都不会等待线程的结束,。
三 MFC中的线程
如果要在MFC程序中产生一个线程,而该线程将调用MFC函数或使用MFC的任何数据,那么必须以AfxBeginThread()或CWinThread::CreateThrad()来产生这些线程。