您的位置:首页 > 编程语言

如何写优雅的代码(4)——简单有效地玩转线程

2011-11-04 19:18 369 查看
//========================================================================

//TITLE:

// 如何写优雅的代码(4)——简单有效地玩转线程

//AUTHOR:

// norains

//DATE:

// Monday 23- November-2009

//Environment:

// WINDOWS CE 5.0

//========================================================================

线程的使用,说复杂吧,却又是只有那几个函数,无非就是通过CreateThread创建线程,然后再通过CloseHanle关闭句柄,大不了再加一个SetThreadPriority来设置优先级;说它简单吧,如何正常退出线程,如何有效地使用线程,却又往往让初学者头疼。



本文主题是如何简单却又有效地使用线程,但不涉及复杂的线程间数据交换。



首先,我们先来了解如何创建线程。很简单,调用CreateThread函数即可。该函数的原型如下:

view plaincopy to clipboardprint?

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpsa,
DWORD cbStack,
LPTHREAD_START_ROUTINE lpStartAddr,
LPVOID lpvThreadParam,
DWORD fdwCreate,
LPDWORD lpIDThread
)

view plaincopy to clipboardprint?

HANDLE hThrd = CreateThread(NULL,NULL,StartAddr,NULL,NULL,NULL);  

HANDLE hThrd = CreateThread(NULL,NULL,StartAddr,NULL,NULL,NULL);


如果后续不需要对该线程进行设置,比如更改优先级之类,那么创建完毕后,我们可以调用CloseHandle关闭句柄:

view plaincopy to clipboardprint?

CloseHandle(hThrd);

view plaincopy to clipboardprint?

DWORD ThreadProc(  

  LPVOID lpParameter  

);  

DWORD ThreadProc(
  LPVOID lpParameter
);




没什么特别的,返回值为DWORD,形参只有一个,为lpParameter。这个lpParameter的数值,就是CreateThread的第四个形参。

我们简单地说说这形参是怎么传递的。



如果我们代码是这么创建线程的:

view plaincopy to clipboardprint?

DWORD dwValue = 123;

CreateThread(NULL,NULL,ThreadProc,reinterpret_cast<VOID *>(dwValue),NULL,NULL);

view plaincopy to clipboardprint?

DWORD ThreadProc(LPVOID pParam)  

{   
  DWORD dwValue = reinterpret_cast<DWORD>(pParam);  

  return 0;   
}  

DWORD ThreadProc(LPVOID pParam)
{
  DWORD dwValue = reinterpret_cast<DWORD>(pParam);
  return 0;
}




此时ThreadProc中的数值即为123。



似乎这形参并没有多大的作用,如果仅仅是为了传递一个DWORD类型的数值,我们完全可以采用全局变量的方式。那我们现在将话题往前推一点,看看在类中封装线程处理函数的情形。这时候,这个看似没多大用的形参,却是我们访问成员变量或函数的唯一桥梁。



在CreateThread的描述中,很清楚知道,我们不能将对象函数的地址作为参数传递,而只能传递类函数。通俗点来说,只有用了static修饰的函数才能作为形参。



如:

view plaincopy to clipboardprint?

class CBase

{
public:
DWORD ThreadProc(LPVOID pParam);

};

view plaincopy to clipboardprint?

class CBase  

{   
public:   
static DWORD ThreadProc(LPVOID pParam);  

};  

class CBase
{
public:
static DWORD ThreadProc(LPVOID pParam);
};




虽然增加static修饰是可以作为形参传递,但我们不可避免会遇到一个问题,就是在ThreadProc中无法访问对象成员或对象函数。解决这个问题也是很简单,我们只需要将this指针作为参数传递给ThreadProc函数,然后再转换为对象指针,就能正常访问对象成员了。



创建线程时:

view plaincopy to clipboardprint?

HANDLE hThrd = CreateThread(NULL,NULL,ThreadProc,this,NULL,NULL);

CloseHandle(hThrd);

view plaincopy to clipboardprint?

DWORD CBase::ThreadProc(LPVOID pParam)  

{   
 CBase *pObj = reinterpret_cast<CBase *>(pParam);  

 if(pObj == NULL)   
{   
 ASSERT(FALSE);   
 return 0x10;  

}   
   
pObj->CheckSum(); //这里就可以直接调用对象函数   

}  

DWORD CBase::ThreadProc(LPVOID pParam)
{
 CBase *pObj = reinterpret_cast<CBase *>(pParam);
 if(pObj == NULL)
{
 ASSERT(FALSE);
 return 0x10;
}

pObj->CheckSum(); //这里就可以直接调用对象函数
}




在类中封装线程函数就是这么简单,关键只在于传递this指针而已。线程的基础差不多就说到这里,如果需要更详细的说明,可以查阅相关文档。只不过,到目前为止的介绍,对于接下来的说明已经足够了。



为了方便,接下来的讨论,我们都假设所有的操作都封装在类里。



之前我们有讨论过,CloseHandle并不是关闭线程,只是将线程的句柄从系统的列表中删除,那么,我们应该如何关闭线程呢?

普遍的,也是最受推荐的,就是让线程自己返回。



比如:

view plaincopy to clipboardprint?

DWORD CBase::ThreadProc(LPVOID pParam)

{
//TODO:Do thing.

...
return 0;

}

view plaincopy to clipboardprint?

DWORD CBase::ThreadProc(LPVOID pParam)  

{   
LABEL1:   
 if(IsTimeOut() == FALSE)  

 {   
  g_iFlag |= MUTEX_NOP;   
}   
   
LABEL2:   
if(IsCheckSystem() == FALSE)  

 {   
  g_iFlag |= MUTEX_CHECK;   
}   
   
LABEL3:   
if(IsBeautiful() == FALSE)  

 {   
  g_iFlag |= BEAUTIFULE;   
}   
   
 return 0;  

}  

DWORD CBase::ThreadProc(LPVOID pParam)
{
LABEL1:
 if(IsTimeOut() == FALSE)
 {
  g_iFlag |= MUTEX_NOP;
}

LABEL2:
if(IsCheckSystem() == FALSE)
 {
  g_iFlag |= MUTEX_CHECK;
}

LABEL3:
if(IsBeautiful() == FALSE)
 {
  g_iFlag |= BEAUTIFULE;
}

 return 0;
}




如果在线程函数还在执行的时候,就调用TerminateThread,那么,最终g_iFlag会是什么数值?



如果执行到LABEL1,刚好调用TerminateThread,那么g_iFlag等于原值;如果是刚好执行到LABEL2,那么g_iFlag会设置MUTEX_CHECK位;如果再往下执行到LABEL3,那么就又和之前的完全不同。



更为重要的是,多线程,你在调用Terminate时,根本无法知道ThreadProc究竟执行到了哪一步。换句话说,这程序,每次实行,都可能会和上一次不一样,这难道不是一个灾难么?



所以,还是老老实实,线程该咋样就咋样,该自己退出就让它自生自灭吧!



线程的使用多种多样,本文无法一一列举,因此接下来的讨论,我们将范围缩小,局限于线程是不停地循环接收事件。



根据该要求我们很简单地罗列出相应的代码:

view plaincopy to clipboardprint?

void CBase::Create()

{
//创建事件

m_hEventWait = CreateEvent(NULL,FALSE,FALSE,TEXT(“EVENT_WAIT”));


//创建线程
m_hThrd = CreateThread(NULL,NULL,ThreadProc,this,NULL,NULL);

}

DWORD CBase::ThreadProc(LPVOID pParam)

{
CBase *pObj = reinterpret_cast< CBase *>(pParam);

if(pObj == NULL)

{
return 0x10;

}

while(TRUE)
{
//等待事件
WaitForSingleObject(pObj->m_hEvnetWait, INFINITE);


//TODO:在这里做接收到事件的动作

}
}

view plaincopy to clipboardprint?

DWORD CBase::ThreadProc(LPVOID pParam)  

{   
CBase *pObj = reinterpret_cast< CBase *>(pParam);  

  if(pObj == NULL)   
  {   
   return 0x10;   
}   
   
while(pObj->m_ExitProc != FALSE)  

{   
//每隔100MS就从函数返回,然后判断是否需要线程退出 
   
if(WaitForSingleObject(pObj->m_hEvnetWait, 100) != WAIT_TIMEOUT)  

{   
//TODO:在这里做接收到事件的动作   

}   
}   
}  

DWORD CBase::ThreadProc(LPVOID pParam)
{
CBase *pObj = reinterpret_cast< CBase *>(pParam);
  if(pObj == NULL)
  {
   return 0x10;
}

while(pObj->m_ExitProc != FALSE)
{
//每隔100MS就从函数返回,然后判断是否需要线程退出
if(WaitForSingleObject(pObj->m_hEvnetWait, 100) != WAIT_TIMEOUT)
{
//TODO:在这里做接收到事件的动作
}
}
}




但这样的修改,其实在效率上还是有点问题的。因为我们需要判断m_ExitProc的数值,所以我们对于WaitForSingleObject需要每隔一段时间就从等待中返回,然后再判断标志位。在这间隔性的返回当中,我们白白耗费了不少CPU时间。



为了避免这种无谓的损耗,我们应该改用WaitForMultipleObjects函数,同时等待两个事件。其中一个事件当然是我们之前所需要的,另外一个新的事件我们称其为唤醒事件,当接收到该事件时,我们就直接退出线程。



根据这个思想,那么我们代码又可以改装如下:

view plaincopy to clipboardprint?

void CBase::Create()

{
//创建唤醒事件

m_hEvent[0] = CreateEvent(NULL,FALSE,FALSE,NULL);

//创建等待事件
m_hEvent[1] = CreateEvent(NULL,FALSE,FALSE,TEXT(“EVENT_WAIT”));


//创建线程

m_hThrd = CreateThread(NULL,NULL,ThreadProc,this,NULL,NULL);

}


DWORD CBase::ThreadProc(LPVOID pParam)

{
CBase *pObj = reinterpret_cast< CBase *>(pParam);

if(pObj == NULL)

{
return 0x10;

}

while(TRUE)
{
//等待多个事件
DWORD dwObj = WaitForMultipleObjects (pObj->m_hEvnetWait, INFINITE);


if(dwObj == WAIT_OBJECT_0)

{
//跳出循环,退出函数

break;
}
else
{
//TODO:在这里做接收到事件的动作

}
}
}

view plaincopy to clipboardprint?

DWORD CBase::ThreadProc(LPVOID pParam)  

{   
CBase *pObj = reinterpret_cast< CBase *>(pParam);  

  if(pObj == NULL)   
  {   
   return 0x10;   
}   
   
//在这里设置线程运行标识   

 InterlockedExchange(reinterpret_cast<LONG *>(&m_bThrdRunning),TRUE);  

   
while(TRUE)   
{   
//等待多个事件    
DWORD dwObj = WaitForMultipleObjects (pObj->m_hEvnetWait, INFINITE);  

   
if(dwObj == WAIT_OBJECT_0)  

{   
 //跳出循环,退出函数   

 break;   
}   
else   
{   
//TODO:在这里做接收到事件的动作   

}   
}   
   
//在这里设置线程退出标识    
InterlockedExchange(reinterpret_cast<LONG *>(&m_bThrdRunning),FALSE);  

return 0;   
}  

DWORD CBase::ThreadProc(LPVOID pParam)
{
CBase *pObj = reinterpret_cast< CBase *>(pParam);
  if(pObj == NULL)
  {
   return 0x10;
}

//在这里设置线程运行标识
 InterlockedExchange(reinterpret_cast<LONG *>(&m_bThrdRunning),TRUE);

while(TRUE)
{
//等待多个事件
DWORD dwObj = WaitForMultipleObjects (pObj->m_hEvnetWait, INFINITE);

if(dwObj == WAIT_OBJECT_0)
{
 //跳出循环,退出函数
 break;
}
else
{
//TODO:在这里做接收到事件的动作
}
}

//在这里设置线程退出标识
InterlockedExchange(reinterpret_cast<LONG *>(&m_bThrdRunning),FALSE);
return 0;
}




看到这里也许有人会觉得奇怪,因为对于m_bThrdRunning变量来说,也只有在线程里才会变更其数值,为什么还要祭出InterlockedExchange呢?对,没错,如果外部只需要读取其数值,而不用更改,那么只要简单地调用等号就好了。那为什么我们还要这么弄呢?主要是考虑到关闭线程的函数。



简单点来说,我们关闭线程的函数应该分为两种模式。一种模式为同步,另一种为异步。换句话来说,当其为异步模式时,我们只需要像线程发送个事件就好了;如果为同步模式,那么发送事件完毕后,我们还要判断退出标识。这时候,InterlockedExchange就派上用场了,我们可以采用它做一个自旋判断,直到其为FALSE,我们才退出关闭函数。



如上所言,则关闭函数如下:

view plaincopy to clipboardprint?

void CBase::Close(CloseFlag flag)

{
if(m_bProcRunning != FALSE)

{
SetEvent(m_hEvent[0]);

if(flag == CLOSE_ASYNC)

{
//为异步模式,世界返回

return;
}

//自旋等待,直到其线程退出

while(InterlockedExchange(reinterpret_cast<LONG *>(& m_bThrdRunning),TRUE) == TRUE)

{
SetEvent(m_hEvent[0]);
Sleep(100);
}
InterlockedExchange(reinterpret_cast<LONG *>(& m_bThrdRunning),FALSE);

}

//关闭线程句柄
CloseHandle(m_hThrd);
m_hThrd = NULL;
}

void CBase::Close(CloseFlag flag)
{
if(m_bProcRunning != FALSE)
{
SetEvent(m_hEvent[0]);

if(flag == CLOSE_ASYNC)
{
//为异步模式,世界返回
return;
}

//自旋等待,直到其线程退出
while(InterlockedExchange(reinterpret_cast<LONG *>(& m_bThrdRunning),TRUE) == TRUE)
{
SetEvent(m_hEvent[0]);
Sleep(100);
}
InterlockedExchange(reinterpret_cast<LONG *>(& m_bThrdRunning),FALSE);
}

//关闭线程句柄
CloseHandle(m_hThrd);
m_hThrd = NULL;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: