Windows线程创建、退出及资源释放

可以通过以下几种方法创建一个线程
1、 CreateThread
2、 _beginthread
3、 _beginthreadex
4、 AfxBeginThread
--------------------------------------------------------------------------------------

1、CreateThread

函数原型


HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
   SIZE_T dwStackSize, // initial stack size
   LPTHREAD_START_ROUTINE lpStartAddress, // thread function
   LPVOID lpParameter, // thread argument
   DWORD dwCreationFlags, // creation option
   LPDWORD lpThreadId // thread identifier);

参数
lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,NULL使用默认安全性,不可以被子线程继承,否则需要定义一个结构体将它的bInheritHandle成员初始化为TRUE
dwStackSize:设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。任何情况下,Windows根据需要动态延长堆栈的大小。
lpStartAddress:指向线程函数的指针,必须以下列形式声明:

DWORD WINAPI ThreadProc ( LPVOID lpParam ) ,格式不正确将无法调用成功。
//也可以直接调用void类型
//但lpStartAddress要这样通过LPTHREAD_START_ROUTINE转换如:(LPTHREAD_START_ROUTINE)MyVoid
//然后在线程声明为:
void MyVoid()
{
return;
}
lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
dwCreationFlags :线程标志,可取值如下:
--------------------------------------------------------------------------------------
CREATE_SUSPENDED(0x00000004):创建一个挂起的线程,
0:表示创建后立即激活。
STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize参数指定初始的保留堆栈的大小,否则,dwStackSize指定提交的大小。该标记值在Windows 2000/NT and Windows Me/98/95上不支持。
--------------------------------------------------------------------------------------
lpThreadId:保存新线程的id。

返回值
函数成功,返回线程句柄;函数失败返回false。 若不想返回线程ID,设置值为NULL。

实例:

PMYDATA pData; pData = (PMYDATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MYDATA)); if( pData == NULL ) ExitProcess(2); // Generate unique data for each thread. pData->val1 = i; pData->val2 = i+100; HANDLE hThread = CreateThread( NULL, // default security attributes 0, // use default stack size ThreadProc, // thread function pData, // argument to thread function 0, // use default creation flags &dwThreadId[i]); // returns the thread identifier // Check the return value for success. if (hThread == NULL) { ExitProcess(i); }


2、_beginthread

函数原型

uintptr_t _beginthread( void( *start_address )( void * ), unsigned stack_size, void *arglist );

参数
start_address: 新线程的起始地址 ,指向新线程调用的函数的起始地址
stack_size新线程的堆栈大小,可以为0
arglist 传递给线程的参数列表,无参数是为NULL 

返回值
假如成功,函数将返回一个处理信息对这个新创建的线程。如果失败_beginthread将返回-1。

引用头文件
#include 

实例

m_hThreadHandle = HANDLE(_beginthread(CAppLog::LogProcThread, 0, this));


3、_beginthreadex

函数原型(MSDN)

unsigned long _beginthreadex( void *security

unsigned stack_size

unsigned ( __stdcall *start_address )( void * ), 

void *arglist

unsigned initflag

unsigned *thrdaddr );

参数
security:安全属性,NULL为默认安全属性
stack_size:指定线程堆栈的大小。如果为0,则线程堆栈大小和创建它的线程的相同。一般用0
start_address :指定线程函数的地址,也就是线程调用执行的函数地址(用函数名称即可,函数名称就表示地址)
arglist:传递给线程的参数的指针,可以通过传入对象的指针,在线程函数中再转化为对应类的指针
initflag:线程初始状态,0:立即运行;CREATE_SUSPEND:suspended(悬挂)
thrdaddr :用于记录线程ID的地址

返回值
与_beginthread()不同的是: _beginthread 返回-1表示失败, 而_beginthreadex()返回0表示失败!

引用头文件
#include

实例:

HANDLE hThread; hThread = (HANDLE)_beginthreadex( NULL, 0, &SIPVoIPLink::ReceivingThrd, (LPVOID)this, 0, &threadID ); if(hThread == NULL) return false;



4、AfxBeginThread

这个看起来就直接了,MFC封装的函数。
在MFC中, 用户界面线程工作者线程都是由AfxBeginThread创建的,MFC提供了两个重载版的AfxBeginThread,一个用于用户界面线程,另一个用于工作者线程,分别有如下的原型和过程:

原型一( 用户界面线程

CWinThread* AFXAPI AfxBeginThread( CRuntimeClass* pThreadClass,    int nPriority,    UINT nStackSize,    DWORD dwCreateFlags,    LPSECURITY_ATTRIBUTES lpSecurityAttrs)

参数
pThreadClass从CWinThread派生的RUNTIME_CLASS类;
nPriority指定线程优先级,如果为0,则与创建该线程的线程相同;
nStackSize指定线程的堆栈大小,如果为0,则与创建该线程的线程相同;
dwCreateFlags一个创建标识,如果是CREATE_SUSPENDED,则在悬挂状态创建线程,在线程创建后线程挂起,否则线程在创建后开始线程的执行。
lpSecurityAttrs表示线程的安全属性,NT下有用。

原型二(工作线程)

CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc,    LPVOID lParam,    int nPriority = THREAD_PRIORITY_NORMAL,    UINT nStackSize = 0,    DWORD dwCreateFlags = 0,    LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL   );//用于创建工作者线程

参数:
pfnThreadProc : 线程的入口函数,声明一定要如下: UINT MyThreadFunction(LPVOID pParam),不能设置为NULL;
pParam : 传递入线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程.
nPriority : 线程的优先级,一般设置为 0 .让它和主线程具有共同的优先级.
nStackSize : 指定新创建的线程的栈的大小.如果为 0,新创建的线程具有和主线程一样的大小的栈
dwCreateFlags : 指定创建线程以后,线程有怎么样的标志.可以指定两个值:
--------------------------------------------------------------------------------------
CREATE_SUSPENDED : 线程创建以后,会处于挂起状态,直到调用:ResumeThread
0 : 创建线程后就开始运行.
--------------------------------------------------------------------------------------
lpSecurityAttrs : 指向一个 SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性.如果为 NULL,

返回值
一个指向新线程的线程对象的指针

以上几种创建线程的区别

其实windows只提供了一个创建线程的方法,就是CreateThread,后面三个函数都是由CreateThread间接得到。
1、_beginthread和_beginthreadex的区别
首先我们看看这两个函数都干了什么:

uintptr_r __cdecl _beginthreadex(...) { //为即将创建的线程分配一个数据结构_ptiddata ptd(per-thread data) //初始化这个数据结构,其中ptd->_thandle = (uintptr_t)(-1) //如果初始化失败,返回(uintptr_t)(0) [_beginthread返回-1] //用传进来的参数,调用CreateThread //如果创建成功返回CreateThread返回的代码 //如果创建失败则释放ptd,并返回(uintptr_t)(0) [_beginthread返回-1,而CreateThread失败返回0,非-1] }

然后再看看这两个函数有什么不同
(1) 参数列表不同, ex版本的参数和CreateThread差不多:
(2) 二者在初始化ptd失败时返回的值不同
(3)_beginthread的参数缺少安全描述符. 而且它是创建线程的时候先以挂起状态创建 (CreateThread会填充ptd->_thandle和ptd->_tid) 然后再ResumeThread。 _beginthread是根据传进来的参数创建线程
(4)失败返回值不同,ex版本的与Windows API CreateThread返回值是一直的,这也是提倡使用后者的原因之一

2、CreateThread()、_beginthreadex()及、AfxBeginThread()
CreateThread时Windows API接口函数, _beginthreadex函数是C/C++运行库提供的函数,从 _beginthreadex函数的源代码,可以看出它的主要动作是:增加了一个名为ptd的 _ptiddata的结构的处理,然后在调用CreateThread函数。_ptiddata是每个线程都拥有自己的专用的数据结构。 AfxBeginThread是MFC封装的启动线程的函数,里面包含了很多和MFC相关的启动信息,而且封装了一些常用的操作,使用起来也比较简便。而用另外两个函数就需要程序员对类型,安全性检查进行更多的思考!

----------------------------------------------------------------------------------------
线程退出方式
1.线程函数返回
2.线程通过调用ExitThread终止自己(自己调用)
3.TerminateThread(本进程或其它进程的线程调用)
4.包含线程的进程终止

第一种是最完美的退出方式,应尽可能使用第一种方法以避免资源泄漏!
----------------------------------------------------------------------------------------
线程资源释放
必须明白,一个线程终止和线程资源释放那个不是一个概念。当一个线程以上面几种退出方式终止之后,线程对象及资源还存在于内存中,所以在线程终止之后还需要某种方式释放资源。

线程的释放方式与创建方式相关联:
CreateThread创建的线程应使用CloseHandle关闭。
_beginthread 创建的线程应使用_endthread关闭。
_beginthreadex创建的线程应使用_endthreadex关闭。
AfxBeginThread 创建的线程,如果是线程自己结束自己的线程( 从CWinThread继承出来的 )就用AfxEndThread。如果外部调用的话可以用PostThreadMessage(m_nThreadID, WM_QUIT,0,0);给这个线程发送消息,线程就会结束的,其中的m_nThreadID是线程ID。

线程资源的释放方法(常用)
1、如果对创建线程的引用不感兴趣,可在创建之后直接关闭句柄

void main()

{

……

HANDLE hThread = CreateThread(NULL,0,ThreadFunc,NULL,0,NULL);

CloseHandle(hThread);

……

}

有些新手可能会有个疑问,为什么在刚创建完线程之后就把句柄关掉了?
要特别注意句柄和实例区别,句柄只是对实例对象的引用,用以操作对象。我们使用CreateThread创建了一个线程实例(可将线程整体看作一个对象),返回的线程句柄只是提供了一个方法(类似于指针)让我们可以访问或操作线程对象。我们这里只需要让创建的线程执行必要的代码段就好了,之后并不需要在对其进行任何操作,所以可以直接将其CloseHandle掉,关闭句柄只是切断了访问线程的方式罢了,线程还是存在并运行着的。
然而,只是这样解释还是有所偏差,因为在很多使用CloseHandle关闭其他对象句柄的操作中都会释放对象占用的资源,而对于线程,在调用CloseHandle之后并不会终止线程,也就不会立马释放线程资源(实现中线程还必须继续运行),调用CloseHandle之后系统会递减线程内核对象的使用计数,当线程执行完毕(线程函数执行完)之后也会递减此线程内核对象使用计数,当计数为0时才会释放线程资源!
反过来看,如果不使用CloseHandle关闭线程句柄,那么系统就会一直保持着对此线程内核对象的引用,这样,即使线程执行完毕,使用计数也不会为0,所以线程资源不会被释放。
因此,CloseHandle关闭线程句柄是释放线程资源的必要途径,但不会影响线程的正常运行!所以CloseHandle的调用位置非常灵活,即可在线程刚创建出来之后紧接着调用,也可以在线程执行完之后才调用!

2、外部线程WaitForSingleObject,发现线程中止运行后,释放线程相关的资源

void main ()
{
  CThread thread ; //一个自己定义的线程封装类,其中有一些成员变量,Start启动线程函数,Release释 放资源函数。
  thread.Start();
  while(WaitForSingleObject(thread.m_hThread,1000) != WAIT_OBJECT_0)
{
  //TerminateThread(thread.m_hThread);//在这里如果你不想再等待,可以中止它
  Sleep(1000);
  }
  thread.Release();
}
这种方式的优点是对线程的全面的管理。
缺陷在于,如果要即时释放资源,必须有一个专门的外部的线程来不断的监视或管理线程运行状态。

3、线程退出时将自身资源释放

DWORD CALLBACK CThread :: Thread ( VOID * pParam ) //线程的入口函数,static函数
{
  CThread *pthread = pParam;
  ... //工作状态设置
  pthread->Execute()
  ... //工作状态设置
  pthread->Release() //在所有的工作做完之后进行释放资源
  return 0;
}
这种方式的优点是对线程的资源的时机是最准确的,缺陷是必须要保证线程在需要退出时不会阻塞。

3、在实际的工作中,我们需要这多种方式的集合来实现我们的功能

如有个网络服务器,它给每个用户的新建一个线程来响应它的请求,如果用户退出,要保证相关资源要完整的释 放,并且服务器本身有停止功能,停止时,不论有多少和用户交互,都必须中止所有线程,且释放所有资源。
要解决的子问题:
Q1:用户退出时资源释放:
解决方式:
(1)使用线程自身退出时释放的方式。
(2)使用一个外部监视线程,发现线程退出时释放相应的资源。

Q2:服务器停止时退出且释放所有线程
设用户线程工作状态为停止, 等待用户线程自然退出,如果没有退出,可能是阻塞状态。这时使用强制退出的方法。
释放所有用户线程相关的资源,可以根据资源的使用时间和方式(如某些Windows资源句柄), 在线程退出之前的某时机进行释放。

下面是我实作的满足以上要求的自身退出方式释放资源的线程类的代码片段:

int CThread :: Stop ()
{
  m_RunningMutex . Lock (); //判断线程的工作状态,如果已经退出,直接返回
  if(!m_bRunning)
  {
  m_RunningMutex.Unlock();
  return ERR_VTHREAD_ALREAD_STOP;
  }
  m_RunningMutex.Unlock();

  m_StopMutex . Lock ();
  m_bStop = True ;   //设置线程的工作状态为停止
  m_StopMutex.Unlock();
#ifdef _WIN32
  DWORD ThreadId = GetCurrentThreadId(); //判断是否线程自身退出,如果是,则返回,不能调用强 制退出的方法
#else
  pthread_t ThreadId = pthread_self();
#endif
 if(ThreadId != m_ThreadID)
  {
  m_dwExitMode = EXIT_BY_OTHER;
  }
  else
  {
  m_dwExitMode = EXIT_BY_SELF;
  }

  if ( m_dwExitMode == EXIT_BY_OTHER ) //如果是服务器停止信号
 {
  int nTryTime = 0;
  bool bStopTry = false;
  while( m_bRunning && !bStopTry) //尝试判断线程是否停止,最好使线程自然退出
  {
    if (nTryTime > m_nStopWaitTime)  bStopTry = true;
    Sleep(10000);
    nTryTime ++;
  }
  if(bStopTry) //如果线程没有退出,就强制退出
#ifdef _WIN32
    TerminateThread(m_hThread, DEF_EXIT_CODE);
#else
    pthread_cancel(m_ThreadID);
#endif
  m_RunningMutex.Lock();
  m_bRunning = False;
  m_RunningMutex.Unlock();

  Release (); //线程已经停止,释放所有私有次源,这行代码可以在服务器端,让此函数只执 行停止功能
  }
  return 0;
}
//这是线程的主函数,它包含的释放自身私有资源的功能。
#ifdef WIN32
DWORD __stdcall CThread::ThreadProc( VOID *lpParameter )
#else
VOID* CThread::ThreadProc( VOID *lpParameter )
#endif
{
 BOOL bStop;
#ifndef _WIN32
  //pthread_detach(pthread_self());
#endif
  CThread* pThread = (CThread*)lpParameter;

  pThread -> m_RunningMutex . Lock ();
  pThread -> m_bRunning = True ;
  pThread -> m_RunningMutex . Unlock ();

  pThread -> m_StopMutex . Lock ();
  bStop = pThread -> m_bStop ;
  pThread -> m_StopMutex . Unlock ();
  if ( ! bStop )
  {
  pThread -> Execute ();
  }
  pThread -> m_RunningMutex . Lock ();
  pThread -> m_bRunning = False ;
  pThread -> m_RunningMutex . Unlock ();
  if ( pThread -> m_dwExitMode == EXIT_BY_SELF ) //在这里判断线程是否自身退出,如是:释放私有资源
  {
  pThread->Release();
 }
 //退出线程,使线程以最自然的方式退出.
  return 0;
}

深入讨论几种创建线程的方式

(1)使用 _beginthreadex 创建的线程就不该用CloseHandle释放, 因为,当用_beginThread来创建,而用CloseHandle来关闭线程时,这时复制的全局结构就不会被释放了,这就有了内存的泄漏。这就是很多资料所说的内存泄漏问题的真正的原因。

(2)不要在一个MFC程序中使用_beginthreadex()或CreateThread()。这句话的意思是由于AfxBeginThread()是MFC封装的启动线程的函数,里面包含了很多和MFC相关的启动信息,而且封装了一些常用的操作,使用起来也比较简便。而用另外两个函数就需要程序员对类型,安全性检查进行更多的思考!

(3)用_beginthreadex()函数应该是最佳选择,因为_beginthreadex()函数是CRun-timeLibrary中的函数,函数的参数和数据类型都是CRun-timeLibrary中的类型,这样在启动线程时就不需要进行Windows数据类型和CRun-timeLibrary中的数据类型之间的转化。减低了线程启动时的资源消耗和时间的消耗!

(4)在C程序中,几乎都要用到new和delete,难道只有使用_beginthreadex()?不,因为MFC也是C类库(只不过是Microsoft的C类库,不是标准的C类库),在MFC中也封装了new和delete两中运算符,所以用到new和delete的地方不一定非要使用_beginthreadex()函数,用其他两个函数都可以!其实在程序中使用上面的哪个函数并不是绝对的,书的作者只不过是提了一个更佳的搭配方法,我在MFC程序中也经常使用_beginthreadex()和CreateThread()这两个函数,运行的效果也没有多大的区别,有的时候只是需要你额外的进行一些类型检查和其他的一些转化操作,其余没有其他不妥! 创建线程只有一个方法是::CreateThread()。_beginthreadex()、AfxBeginThread()等内部都是调用这个函数的,因为操作系统只提供这一个接口C静态库比WINDOWS出来还早,就别提多线程了,所以他对多线程的支持不是很好,但后悔也来不急,但也不能怪人家。

(5)C运行库_beginthreadex()。他经过一些处理后,再调用CreateThread()如果要强制结束的话也最好用_endthreadex结束,因为他也要一些处理。 总结上面的内容,当然《Windows核心编程》上面得说法是比较权威的。所以,在对线程的结构、运行还不是很了解的时候最好还是按照书上的来。这样能够避免一些可能出现的莫名奇妙的错误,也省去的一些其他结构处理的考虑。当你清楚地知道线程的结构与运行机制,以及了解各个函数对CreateThread函数的封装的时候,大概那时候就能够应用自如了

你可能感兴趣的:(C&C++语言,多线程操作)