线程类是对线程的OO封装,常见的有MFC的CWinThread和垮平台的PThread库。我不打算讨论他们的优劣,仅仅探讨不同的设计模式对使用者的影响。
第一类大家都很熟悉,用虚函数接口,CWinThread就是例子,它有一个虚函数叫做Run(),要做不同的实现,就从CWinThread派生,并重载Run()。我给出的例子也是类似(http://blog.youkuaiyun.com/DoItFreely/archive/2006/08/11/1050103.aspx)。
使用如下:
class CWorkerThread : public CWinThread
{
virtual int Run(){...}
....
};
第二类使用接口类。在线程封装类里面保留一个接口类的指针,Java里面线程类就是这样。以下是示意
struct Runnable
{
virtual void Run()=0;
};
class CThread
{
Runnable *m_runnable;
static DWORD OnRun(CThread *This)
{
This->m_runnable->Run();
return 0;
}
public:
CThread() : m_runnable(0){}
~CThread(){}
BOOL Run(Runnable *runnable)
{
m_runnable = runnable;
....//启动线程,并运行OnRun
return TRUE;
}
};
使用如下:
class CClient : public Runnable
{
CThread m_worker;
virtual void Run(){...}
void Foo(){m_worker.Run(this);}
...
};
第三类使用静态成员函数指针Function Pointer (Functor),象这样:
class CThread;
typedef void (CALLBACK *ThreadProc)(CThread *,void *);
class CThread
{
void *m_client;
ThreadProc m_pfn;
static DWORD OnRun(CThread *This)
{
(*This->m_pfn)(this,m_client);
return 0;
}
public:
CThread() : m_client(0),m_pfn(0){}
~CThread(){}
BOOL Run(ThreadProc pfn,void *client)
{
m_pfn = pfn;
m_client = client;
....//启动线程,并运行OnRun
return TRUE;
}
};
使用如下:
class CClient
{
CThread m_workerA;
CThread m_workerB;
void OnRunWorkerA(CThread *thread){...}
static void CALLBACK stubOnRunWorkerA(CThread *thread,CClient *This)
{
This->OnRunWorkerA(thread);
}
void OnRunWorkerB(CThread *thread){...}
static void CALLBACK stubOnRunWorkerB(CThread *thread,CClient *This)
{
This->OnRunWorkerB(thread);
}
void Foo()
{
m_workerA.Run((ThreadProc)&CClient::stubOnRunWorkerA,this);
m_workerB.Run((ThreadProc)&CClient::stubOnRunWorkerB,this);
}
};
第四类是上面的变种,使用非静态的成员函数指针,以减少函数名字。由于成员函数有类型,需要一点“肮脏”的转换(dirty cast)
#pragma warning(disable:4035)//函数没有返回值的编译警告:返回值已经在eax中了,暂时它
template
T CastTo(X x){__asm mov eax,x}
#pragma warning(default:4035)//回复没有返回值的编译警告
class CThread;
typedef void (CThread::*ThreadProc)(CThread *);
class CThread
{
void *m_client;
ThreadProc m_pfn;
static DWORD OnRun(CThread *This)
{
CThread *p = (CThread *)This->m_client;
(*p->m_pfn)(this);
return 0;
}
public:
CThread() : m_client(0),m_pfn(0){}
~CThread(){}
BOOL Run(ThreadProc pfn,void *client)
{
m_pfn = pfn;
m_client = client;
....//启动线程,并运行OnRun
return TRUE;
}
};
使用如下:
class CClient
{
CThread m_workerA;
CThread m_workerB;
void OnRunWorkerA(CThread *thread){...}
void OnRunWorkerB(CThread *thread){...}
void Foo()
{
m_workerA.Run(CastTo(&CClient::stubOnRunWorkerA),this);
m_workerB.Run(CastTo(&CClient::stubOnRunWorkerB),this);
}
};
第五类是第四类的变种,使用Functor对象封装(非静态)成员函数。
#pragma warning(disable:4035)//函数没有返回值的编译警告:返回值已经在eax中了,暂时它
template
class Functor
{
long fn;
void *This;
public:
Functor() : fn(0),This(0){}
Functor(const Functor& f) : fn(f.fn),This(f.This){}
Functor& operator=(const Functor& f)
{
fn = f.fn;
This = f.This;
return *this;
}
template
Functor(void *t,F fp) : This(t)
{
long f = 0;
__asm
{
mov eax,DWORD PTR fp
mov f,eax
}
fn = f;
}
void* GetThis() const{return This;}
void* SetThis(void *That) const{return (This=That);}
long Get() const{return fn;}
template
void Set(void *t,F fp)
{
This = t;
long f = 0;
__asm
{
mov eax,DWORD PTR fp
mov f,eax
}
fn = f;
}
T Call(void) const
{
long fp = fn;
void *t = This;
__asm
{
mov ebx,DWORD PTR fp
mov ecx,t
call ebx
}
}
template
T Call(A a) const
{
long fp = fn;
void *t = This;
__asm
{
mov eax,a
push eax
mov ebx,DWORD PTR fp
mov ecx,t
call ebx
}
}
template
T Call(A a,B b) const
{
long fp = fn;
void *t = This;
__asm
{
mov eax,b
push eax
mov eax,a
push eax
mov ebx,DWORD PTR fp
mov ecx,t
call ebx
}
}
...
};
#pragma warning(default:4035)//回复没有返回值的编译警告
class CThread : public Functor
{
static DWORD OnRun(CThread *This)
{
This->Call(This);//VC 6编译运行通过,写成This->Call<>(This)反而不行,VC2005好像编不了
return 0;
}
public:
CThread(){}
~CThread(){}
BOOL Run(const Functor& fn)
{
*this = fn;
....//启动线程,并运行OnRun
return TRUE;
}
};
使用如下:
class CClient
{
CThread m_workerA;
CThread m_workerB;
void OnRunWorkerA(CThread *thread){...}
void OnRunWorkerB(CThread *thread){...}
void Foo()
{
m_workerA.Run(Functor(this,&CClient::stubOnRunWorkerA));
m_workerB.Run(Functor(this,&CClient::stubOnRunWorkerB));
}
};
总结:
第一类,虚函数接口,接口最简单,同一个对象不支持多个线程实例(除非增加Run()的参数区别每个线程实例),不同的工作线程需要不同的派生类,造成类名称污染。
第二类,封装类接口,接口比较简单,不支持多个线程实例(同上),只需要一个工作线程时很合适。
第三类,感觉跟面对WINAPI一样,使用全局函数(或静态成员函数)把执行流程导回类的成员还是不太爽,有函数名称污染。
第四类,那些转换看着有点不爽,修改OnRun...()接口时要特别小心,不一致了也不会有编译警告或错误。
第五类,更OO一些,缺点也是实现/修改接口困难(我的口号是:一步到位,别改来改去),外加汇编代码对机器的依赖性。
我个人还是比较喜欢第五类,简洁,高效。类库写好之后,所需要做的就是业务逻辑。机器有变化时只要能移植线程库,应用层的代码不需要任何变化。