昨天在看老工程中的代码的时候,发现其中创建了一个线程后,并没有通过CloseHandle函数来关闭该句柄。抱着怀疑的态度,就查了些相关的资料,现把自己的调查结果总结如下。
1、创建线程
可以通过调用CreateThread函数来创建一个线程,函数原型如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpsa,
DWORD cbStack,
LPTHREAD_START_ROUTINE lpStartAddr,
LPVOID lpvThreadParam,
DWORD fdwCreate,
LPDWORD lpIDThread
);
此乃Windows API,MSDN中有详细介绍,在此就不细说了。就说明两点:
一是参数lpStartAddr,这是线程开始的地址,也就是新创建的线程开始执行的地方,一般将一个函数(线程函数)的地址传递给该参数。
二是参数lpvThreadParam,这是传递给一中所说的线程函数的参数。
调用CreateThread时,系统会创建一个线程内核对象,函数CreateThread返回的句柄,可以看作是线程内核对象的一个地址,通过该句柄,你可以操作该线程对象。线程对象的默认引用计数为2,一个是句柄,另一个是线程本身。
2、线程函数
线程函数的类型并无限制,返回值可以是任意类型,自己定义的类型也可以。参数也无限制,你甚至可以定义多个参数。不过,由于创建线程的时候,只传入了一个LPVOID类型的参数,所以,定义的线程函数最好也只包含一个LPVOID类型的参数,如果需要多个参数,可以传递一个结构体指针,然后强制转换。
3、结束线程
结束线程共有四中方法,线程函数返回、ExitThread函数、TerminateThread函数、在进程终止运行时撤消线程。方法间的差别在《Windows核心编程》中有详细介绍,我在此就不重复了。在此,我只说明其中要注意的地方。
最好结束线程的方法就是通过线程函数返回,这样可以释放掉所有的资源,包括系统资源以及C++资源。可以通过函数返回值来设置线程退出码(Exit Code)。
可以在线程函数中调用ExitThread函数来结束自己,其参数为线程退出码(Exit Code)。通过ExitThread函数结束线程时,可以释放系统资源,但是不能释放C++资源,如:若在线程函数中定义了一个类的对象,若通过ExitThread函数结束线程,则不会调用该对象的析构函数,需要在其析构函数中释放的资源将不能被释放。
通过TerminateThread函数,可以强制结束一个线程,其中第一个参数是线程句柄,第二个参数是线程退出码(Exit Code)。通过ExitThread函数结束线程,线程的栈将被销毁,而通过TerminateThread函数结束线程,线程的栈将不被销毁,因为线程是被强制结束的,系统认为可能有其他线程还会使用到该线程的资源。
4、线程内核对象的销毁
当线程内核对象的引用计数为0时,系统将销毁线程内核对象。也就是说,线程内核对象的销毁必须满足两个条件:
一是线程所有的句柄都被close掉(通过函数CloseHandle)。
二是线程必须已经被结束。
若线程正在运行,此时,虽然通过CloseHandle关闭了所有与线程相关的句柄,线程内核对象仍然不会被销毁,只有等线程结束时,线程内核对象才会被销毁。
若未通过CloseHandle关闭线程句柄,虽然线程结束了,线程内核对象仍然不会被销毁。
调用CloseHandle,将会释放Handle占用的资源,同时将线程内核对象的引用计数减一。
结束线程的运行,也会将线程内核对象的引用计数减一。
下面是我的测试代码,在windows xp下,通过vs 2005创建一个win32的控制台程序。
通过测试发现,如果标记1的代码被注释掉,通过任务管理器查看(选择进程选项卡,查看->选择列,选中“句柄计数”),发现句柄数比正常要多一个,也就是说线程句柄没有被释放,自然,线程内核对象也没办法在进程退出前被销毁。
若标记2的代码未被注释掉,则类TestClass的析构函数将不会被执行。
// ThreadTest_Win32.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
char QUIT_EVENT_NAME[] = "Quit Event";
void MainFunction( void );
int SubThreadFunction( void *pContext );
int _tmain(int argc, _TCHAR* argv[])
{
printf( "Entry Main/n");
MainFunction();
printf( "Exit Main/n");
return 0;
}
void MainFunction()
{
// Create thread
HANDLE hSubThread = ::CreateThread(NULL, 0, ( LPTHREAD_START_ROUTINE )SubThreadFunction, NULL, 0, NULL);
if ( NULL == hSubThread ) {
return;
}
// Create quit event
HANDLE hQuitEvent = ::CreateEvent( 0, FALSE, FALSE, (LPCWSTR)QUIT_EVENT_NAME );
if ( NULL == hQuitEvent ){
::CloseHandle( hSubThread ); //Should call CloseHandle to close Handle. 标记1
return;
}
for ( INT i = 0; i < 10; ++i ) {
printf( "Main Thread/n" );
Sleep( 1000 );
}
::SetEvent( hQuitEvent );
::CloseHandle( hQuitEvent );
::CloseHandle( hSubThread ); //Should call CloseHandle to close Handle. 标记1
return;
}
class TestClass
{
public:
TestClass()
{
printf( "TestClass:Constructor/n" );
}
~TestClass()
{
printf( "TestClass:Distructor/n" );
}
};
int SubThreadFunction( PVOID pContext )
{
TestClass object;
// Create quit event
HANDLE hQuitEvent = ::CreateEvent( 0, FALSE, FALSE, (LPCWSTR)QUIT_EVENT_NAME );
if ( NULL == hQuitEvent ){
return 1;
}
// Wait for quit
while( TRUE ) {
printf( "Sub Thread/n" );
DWORD dwWaitResult = ::WaitForSingleObject( hQuitEvent, 1000 );
if ( WAIT_OBJECT_0 == dwWaitResult ) {
::CloseHandle(hQuitEvent);
// ::ExitThread( 0 ); // If use ExitThread to end the thread, the distructor of TestClass will not be called. 标记2
return 100;
}
}
}