有时候,由于设计上的需要,我们通常要把一个类成员函数以线程方式启动,而最常见的实现方式有两种:1.直接把成员函数声明为静态,使得CreateThread等函数可以直接调用;2.声明一个静态成员函数,作为代理,在内部在调用类的非静态成员函数。
这两种方法只是单单为了实现目的而出现的,优点几乎没有,但缺点却显而易见:对于方法1,在静态成员函数内肯定会出现强制类型转换得到pThis,然后一堆堆的pThis->XXX;对于方法2,作为代理的静态成员函数没有复用性,如果新设计一个线程,又要再写一个代理函数,重复劳动。
而另一个可以接受的方案是编写一个线程基类,提供一个类似run的虚函数,派生类实现run函数。但这个方案同样存在不足:首先,用户的类必须派生于这个线程类,从而引入了耦合;其次,如果用户的类有多个成员函数需要以线程启动,那么,线程基类单单一个run虚函数是不能满足的,也就是说,线程基类制约了用户类的能力。
线程基类的不足暴露了入侵式程序框架的缺点,所谓入侵式程序框架是指:必须按照某种规则编写功能代码,才能获得框架某种能力的支持。入侵式框架制约设计者思维,限制功能,扩展性差。一个典型的入侵式框架例子就是设计模式的template method。
相对应地,也存在非入侵式程序框架:编码规则随意,框架不约束,只要用户的类被框架管理后,就能获得某种能力的支持。典型的非入侵式框架就是spring ioc容器,只要用户类被ioc容器管理,就能获得诸如singleton、prototype和aop能力。
在程序设计领域,如果能用非入侵方式实现的功能,就不应该使用入侵方式实现。回到类成员函数线程化这个问题上:有没有一个简便的方法,以一致的方式,在不改变类的定义前提下(也就是所谓的非入侵方式),使线程可以适配于任意类的非静态成员函数?有,这就是本文介绍的成员函数线程适配器。
其实,成员函数线程适配器实现起来很简单,就是把方法2的静态成员函数用模板进行参数化,使其通用性得以提高。具体步骤有3,首先,需要把非静态成员函数以及对象实例保存起来;其次,需要实现一个通用的threadproc用作线程函数,函数内部调用已保存的成员函数,前两步都是通过模板实现;最后需要一些安全机制来保证线程能安全启动。来,直接看代码:
template<typename Result, typename T> class mem_fun_thread_t { public: mem_fun_thread_t(Result ( T::* _Pm )( ),T &inst):mFunDetail(_Pm),tInstance(inst) { hEvent = CreateEvent(NULL,0,0,NULL); }
HANDLE BeginThread() { HANDLE hThread = CreateThread(0,0,this->thread_proc,this,0,0); if(hThread == NULL) { return NULL; } WaitForConstruct(); return hThread; }
~mem_fun_thread_t() { CloseHandle(hEvent); } private: HANDLE hEvent; T& tInstance; mem_fun_ref_t<Result,T> mFunDetail;
static DWORD __stdcall thread_proc(void* p) { mem_fun_thread_t<Result,T>* tmp = (mem_fun_thread_t<Result,T>*)p; T& inst = tmp->tInstance; mem_fun_ref_t<Result,T> funDetail = tmp->mFunDetail; SetEvent(tmp->hEvent); funDetail(inst); return 0; }
void WaitForConstruct() { WaitForSingleObject(hEvent,INFINITE);
} }; |
mem_fun_thread_t就是主角,它的构造函数保存了模板参数T的非静态成员函数及T的一个实例,另外也创建了一个自动复位事件,这个事件的作用稍后再说。
BeginThread就是客户程序用以启动线程的接口,其内部使用CreateThread创建线程,线程参数分别是thread_proc和this。当CreateThread完,函数将调用WaitForConstruct等待,等待什么呢,我在讲述自动复位事件的时候一并解答。当等待完成后,函数退出。
再看thread_proc内部,首先把函数参数(即CreateThread的参数this,也就是mem_fun_thread_t自己)复制一份到函数局部变量,然后事件置位。当事件置位后,BeginThread就可以返回了。事件置位后,使用局部变量内的对象实例调用成员函数。
这里解释一下,首先,为什么要复制一份this数据到线程函数的局部变量,这是因为this保存了成员函数及对象实例,我们要保证这些数据在线程执行时是有效的,如果不复制到线程函数局部变量内,这些数据很可能在客户程序调用完BeginThread之后就无效,例如适配器是一个临时变量:mem_fun_thread_t<bool,Test>(&Test::func,test).BeginThread();其次,我们也要保证这些数据在复制过程中也是有效的,要等到复制完成才能够销毁,所以,BeginThread内就出现了WaitForConstruct。WaitForConstruct直到thread_proc内调用SetEvent才返回,而当SetEvent时,数据已成功复制到thread_proc内。
所以,正常情况下,当BeginThread返回时,可以保证1.线程已启动;2.线程所需数据已安全地保存起来。
好,来看客户程序如何使用:
class Test { public: bool func() { return true; }
void ActivatePrivateFunc() { CloseHandle(mem_fun_thread_t<bool,Test>(&Test::privateFun,*this).BeginThread()); } private: bool privateFun() { return false; } };
int main(int argc, char* argv[]) { Test test;
CloseHandle(mem_fun_thread_t<bool,Test>(&Test::func,test).BeginThread());
return 0; } |
上面的代码演示了如何令一个public无参及private无参成员函数成为线程。当然了,你也可以用mem_fun_thread_t创建对象并保存该对象,直到有需要时才调用BeginThread,另外BeginThread也可以调用多次,创建多条线程。
不足之处,这个适配器只能适配非const成员函数,如果是const成员函数,则需要写一个const的适配器,就像mem_fun_t和const_men_fun_t的关系一样。另外,大家可能已发现,这个适配器只能适配无参成员函数,是的,其实如果要实现有参数版本,可以修改代码,把mem_fun_ref_t替换成mem_fun1_ref_t,然后添加模板参数及成员变量(暂且把修改好的叫做mem_fun1_thread_t)。是的,修改后也只能适配单参成员函数,如果两个参数的话,你需要借助boost库,如果有更多参数呢?
一般情况下,用作线程的成员函数,它所需要的数据都可以从自身对象内获取,所以我认为,无参的成员函数已经满足需要。
最后,大家可能对mem_fun_ref_t不太熟悉,我这里简单介绍一下。mem_fun_ref_t在标准库的functional头文件内,其作用是把一个类成员函数适配成一个单参函数对象(function object或functor),例如:object.func()被它适配后变形为functor(object)。对比起函数指针的难看难理解,函数对象就很有优势,它拥有OOP的一切优点。另外,这里其实出现了一个不太普及的程序设计理念(但它源于古老的理论lambda calculus):functional programming,中文叫函数式编程,简称FP。虽然标准库functional头文件只提供了一些基本的FP设施,但一些经典的FP手法还是可以重现的,例如bind1st把一个双参函数适配成一个单参函数对象(FP把这种转换称之为偏函数化)。对FP有兴趣的话可以参考http://en.wikipedia.org/wiki/Functional_programming。
题外话,mem_fun(x)_thread_t,x>=0其实可以实现得更华丽一点,就是BeginThread函数用operator()代替,如果这样做的话将会出现以下代码:
typedef mem_fun1_thread_t<bool,Test,int> TestThreadFunc; TestThreadFunc testFunc = TestThreadFunc(&Test::func1,test); HANDLE hThread = testFunc(10); |
看上去testFunc就像一个普通单参函数调用,但实际上已经启动了线程^_^且该线程使用testFunc的参数调用一个类的成员函数。类似testFunc这种对象在FP内称为closure。附上mem_fun1_thread_t代码:
template<typename Result, typename T,typename Param> class mem_fun1_thread_t { public: mem_fun1_thread_t(Result ( T::* _Pm )(Param ),T &inst):Pm(_Pm),tInstance(inst) { hEvent = CreateEvent(NULL,0,0,NULL); }
HANDLE operator()(const Param& param) { this->tParam = param; HANDLE hThread = CreateThread(0,0,this->thread_proc,this,0,0); if(hThread == NULL) { return NULL; } WaitForConstruct(); return hThread; } ~mem_fun1_thread_t() { CloseHandle(hEvent); } private: HANDLE hEvent; T& tInstance; Param tParam; mem_fun1_ref_t<Result,T,Param> Pm;
static DWORD __stdcall thread_proc(void* p) { mem_fun1_thread_t<Result,T,Param>* tmp = (mem_fun1_thread_t<Result,T,Param>*)p;
T& inst = tmp->tInstance; Param param = tmp->tParam; mem_fun1_ref_t<Result,T,Param> funcDetail = tmp->Pm;
SetEvent(tmp->hEvent); funcDetail(inst,param); return 0; }
void WaitForConstruct() { WaitForSingleObject(hEvent,INFINITE); } }; |