zoukankan      html  css  js  c++  java
  • 成员函数线程适配器

        有时候,由于设计上的需要,我们通常要把一个类成员函数以线程方式启动,而最常见的实现方式有两种: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);

         }

    };

  • 相关阅读:
    Caused by: java.lang.ClassNotFoundException: org.apache.commons.fileupload.RequestContext
    Caused by: java.lang.ClassNotFoundException: org.apache.commons.lang3.StringUtils
    Caused by: java.lang.ClassNotFoundException: org.apache.commons.io.FileUtils
    Caused by: java.lang.ClassNotFoundException: javassist.ClassPool
    Caused by: java.lang.ClassNotFoundException: ognl.PropertyAccessor
    利用DBLINK同步表数据库--老刘
    100万并发连接服务器笔记之1M并发连接目标达成
    模拟row cache lock
    redis读写性能测试
    Adobe RIA 开发工程师认证考试大纲
  • 原文地址:https://www.cnblogs.com/rickerliang/p/2025066.html
Copyright © 2011-2022 走看看