CSDN博客

img rabbit2003

两种面向对象的C++线程模型

发表于2003/8/15 16:32:00  1673人阅读

分类: VC/C++

两种面向对象的C++线程模型

 

摘要:本文首先分析对比了JavaC#的线程模型,然后在C++中实现了类似Java的轻量级线程模型 CJThread/IRunable,并在此基础上利用模板和operator->*实现类似C#的线程模型CCSharpThread。本文重点推荐CCSharpThread模型,它不仅是类型安全的,还能将任意一个原型为DWORD()的公有非静态类成员方法作为线程方法。

 

关键词C++;模板;operator->*;线程;线程模型

 

       面向对象的线程模型使开发人员能以面向对象的方法来看待线程,并以面向对象的方法实现线程的应用逻辑;它包含两个要素,封装线程逻辑(如线程的创建、销毁、管理等)的线程对象和实现线程应用逻辑的线程方法。C++本身没有提供面向对象的线程模型。目前常用的Win32 ThreadPosix Thread都只提供C界面的API(简称线程API),并且只能用普通C函数作为线程方法。C++的非静态类成员方法由于包含一个隐含的this参数而不能用作线程方法,使得C++程序员开发轻量级(指不采用MFCVCL等类库)多线程应用时不能利用面向对象的方法看待线程。本文首先分析对比了JavaC#的线程模型,然后在C++中实现了类似Java的轻量级线程模型 CJThread/IRunable,并在此基础上利用模板和operator->*实现类似C#的线程模型CCSharpThread。本文重点推荐CCSharpThread模型,因为它不仅是类型安全的,还能将任意一个原型为DWORD()的公有非静态类成员方法作为线程方法。本文中线程方法是指实现线程应用逻辑的函数。在C++中既可以是全局函数,也可以是类成员方法。

1.      JavaC#线程模型分析对比

       JavaC#都提供了面向对象的线程模型。它们都抽象出了线程对象,而开发人员在某个类的成员方法中实现线程应用逻辑。通过分离线程对象和线程方法,简化了线程应用逻辑的开发。

Java中开发线程有两种方法。第一种方法是由Thread派生一个线程类,实现run方法,并调用Thread.start方法启动线程,如:

class MyThread extends Thread { //创建线程类

public void run() {} //线程方法

}

MyThread aThread = new MyThread(); //创建一个线程对象

aThread.start() ; //启动线程

第二种方法是通过实现Runable接口创建一个可执行类,并利用一个Thread对象来启动线程,如:

         class MyRunable implements Runnable{

public void run() {…} //线程方法

}

MyRunable aRunable = new MyRunable(); //创建一个可执行对象

         Thread aThread = new Thread(aRunable);                 //创建一个线程对象,并与可执行对象关联

         aThread.start()    ;        //启动线程

C#的线程模型将线程对象和线程方法分离得更彻底,它可将任何一个原型为void( )的公有类成员方法(静态或非静态)用作线程方法;另外线程启动时还指定一个提供线程方法的对象(该对象提供线程应用逻辑所需的各种信息)。下面是一个简单的例子:

using System;

using System.Threading;

public class ThreadWork {  //ThreadWork不显式继承任何类,DoWork可作为线程方法

   public void DoWork() { for(int i = 0; i<10 ;i++) Console.WriteLine("Working thread..."); }   //End of DoWork

}   //End of ThreadWork

class ThreadTest{

   public static void Main() {   //将对象aThread DoWork方法作为线程方法执行

ThreadWork aThread = new ThreadWork();

ThreadStart myThreadDelegate = new ThreadStart(aThread.DoWork);  //指定线程方法

Thread myThread = new Thread(myThreadDelegate);  //创建线程对象

myThread.Start();  //启动线程

    }   //End of Main

  }  //End of  ThreadTest

开发人员最关心的是如何实现线程方法。Java线程模型提供两种方法来实现线程方法,重载Runable.run方法或者重载Thread.run方法。开发人员可根据具体的应用场合选择合适的基类(Runable或者Thread),这是Java线程模型的一个优点。另外可以看到,Java线程模型中只能在RunableThread的子类中实现线程方法(即子类的run方法),而且每个子类只能实现一个线程方法。C#线程模型由于允许将任何一个原型为void( )的公有类成员方法(静态或非静态)用作线程方法,因此它实际上允许在任何一个类(不要求这个类是某个类的子类)中实现线程方法,而且同一个类中可以实现多个线程方法。所以我们认为C#的线程模型更灵活,而这种灵活性使得开发人员能够将程序结构组织的更加清晰、合理。

C++没有直接提供面向对象的线程模型。本文将通过封装C界面的线程API,在C++中实现类似Java的轻量级线程模型CJThread/IRunable,并在此基础上利用模板和operator->*实现了类似C#的线程模型CCSharpThread。为简单起见,本文的只给出Win32中的实现,且给出的代码重点在于突出线程模型的特点,忽略错误检查、完备性等。本文中所有的代码在VC++ 6.0中编译并调试通过。

2.      CJThread/IRunable线程模型

CJThread/IRunable是类似Java的线程模型。首先定义IRunable接口,其中包含线程方法Run。然后由IRunable派生CJThreadCJThread实现空Run方法,并实现线程相关的逻辑(创建、启动线程等)。CJThread对象可以与一个IRunable对象关联起来,也可以不关联。线程启动时,如果线程对象与一个IRunable对象RunObj关联,则线程体CJThread::ThreadBody将控制权交给RunObj->Run;否则线程体CJThread::ThreadBody将控制权交给自己的Run方法。使用者即可直接由CJThread派生并重载Run方法实现线程方法,也可通过实现接口IRunable来实现线程方法(这与Java类似)。

class CJThread;          //预声明

class IRunable{           //执行接口

protected:

         virtual DWORD Run()=0;   //线程方法,由子类实现

         friend class CJThread;   //允许CJThread调用Run方法,但不允许其他人从外部调用

};

class CJThread : public IRunable{          //线程类

         IRunable *m_runobj ;                      //线程关联的执行对象

         HANDLE m_hThread;                             //线程句柄

         static DWORD WINAPI ThreadBody(LPVOID param){

                   CJThread *pThis = (CJThread *)param ;

                   IRunable *runobj = pThis->m_runobj ? pThis->m_runobj : pThis;  //确定IRunable对象

                   return runobj ->Run();          //将线程控制权交给IRunable.Run

         }

protected:

DWORD Run(){ return 0 ; }   //实现线程方法,以便能实例化CJThread

public:

         CJThread(IRunable *runobj = NULL):m_runobj(runobj){};   //构造方法,给m_runobj赋值

         bool Start(){   //创建线程并启动。这里忽略错误检查,例如线程已经启动等

                  m_hThread = CreateThread(NULL,0,CJThread::ThreadBody,(LPVOID)this,0,NULL);

                  return NULL != m_hThread;

         }

         void Join(){ WaitForSingleObject(m_hThread,INFINITE);}   //等待线程退出

};

3.      CCSharpThread线程模型

实现CCSharpThread线程模型需要借助模板和操作符operator->*。如果读者对operator->*感到陌生,可以参考《程序员》杂志2001年第976页上的《为智能指针实现operator->*》这篇文章,其中对operator->*作了很好的说明。CCSharpThread CJThread/IRunable为基础,下面是CCSharpThread的实现:

template <class T>

class CCSharpThread : public CJThread{

         typedef DWORD (T::*ThreadFunc)();

         ThreadFunc ThreadBody;     //线程方法,是模板参数类的一个原型为DWORD ()的非静态成员方法

                                                        //具体是哪个方法可在运行时指定

         T *m_TheadFuncObj;           //提供线程方法的对象

protected:

         virtual DWORD Run(){return (m_TheadFuncObj->*ThreadBody)();}        //重载线程方法

public:

         CCSharpThread (T &obj,ThreadFunc _threadfunc): m_TheadFuncObj(&obj),ThreadBody(_threadfunc) {}

};

CCSharpThread实际上是一个线程模板,利用CCSharpThread可将任何一个原型为DWORD()的公有非静态类成员方法指定为线程方法(你也可以修改这个原型)。构造线程对象需要两个参数,一个是线程方法对象obj,一个是线程方法_threadfunc;运行时真正的线程方法是obj. _threadfunc()。所以CCSharpThread本质上是将指定对象的指定方法作为线程方法,这是一个非常有用的概念。

4.      实例

现在通过一个例子来演示CJThread/IRunableCCSharpThread两个线程模型的用法,同时体会一下CCSharpThread模型的强大。假设在某个应用中,需要启动两个线程,一个线程将一个变量从0每次加1增加到3,然后退出,另外一个线程监视这个变量,当这个变量为3时就退出。下面分别用CJThread/IRunableCCSharpThread来实现这个应用程序。

例1,       CJThread/IRunable Version

int counter = 0;

class JAppThread : public CJThread{

protected:

         virtual DWORD Run(){

                   for( ; counter < 3 ; counter++)

                            {cout << "JAppThread: 计数器加1,当前值为counter = " << counter << endl ;  Sleep(1000); }

                   return 0 ;

         }

};

class JRunable : public IRunable{

protected:

         virtual DWORD Run(){

                   while(counter < 3) { cout << "JRunable: 检查计数器是否到3" << endl ; Sleep(500); }

                   cout << "JRunable 检测到计数器达到3,退出" << endl ;

                   return 0 ;

         }

};

void JThread_Test(){

         JRunable runobj;

         CJThread   thread1(&runobj);

         JAppThread thread2;

         thread1.Start();   //启动线程

         thread2.Start();   //启动线程

         thread1.Join();   //等待线程退出

         thread2.Join();   //等待线程退出

}

例2,       CCSharpThread Version

class App2{

         int _counter ;

public:

         App2(){ _counter = 0 ;}

         DWORD Thread1(){

                   while(_counter < 3) {cout << "Thread1: 检查计数器是否到3" << endl ;   Sleep(500);}

                   cout << "Thread1 检测到计数器达到3,退出" << endl ;

                   return 0 ;

         }

         DWORD Thread2(){

                   for( ; _counter < 3 ; _counter++){

                            cout << "Thread2: 计数器加1,当前值为counter = " << _counter << endl ;

                            Sleep(1000);

                   }

                   return 0 ;

         }

};

void CSharpThreadTest(){

         App2 appObj;

         CCSharpThread<App2> thread1(appObj,App2::Thread1);

         CCSharpThread<App2> thread2(appObj,App2::Thread2);

         thread1.Start();  //启动线程

         thread2.Start();  //启动线程

         thread1.Join();  //等待线程退出

         thread2.Join();  //等待线程退出

}

对比上面的两个例子可以发现,例1中为实现两个线程,定义了两个类;为了让这两个类都能访问计数器counter,我们将counter定义为全局变量。例2只定义了一个类App2,并在其中实现了两个线程方法;同时例2将计数器_counter定义为App2的私有变量。显然不论从程序的简洁性还是从程序结构的合理性来看,例2都要优于例1。另外由于同时利用App2的两个非静态成员方法作为线程方法,就不必象传统方法一样将_counter定义成静态变量。换句话说,例2中的两个线程实现了在对象级共享信息,这是CCSharpThread的一个非常有用的特性,善用这个特性开发人员可以使自己的程序结构变得更加清晰、合理。

5.      类型安全

CJThread/IRunable模型的类型安全体现在它要求CJThread只能和一个IRunable对象(实现IRunable接口的对象)关联。由于利用了模板,CCSharpThread模型是真正类型安全的,例如编译下面的代码:

class App3{

public  :  

DWORD InvalidThreadBody(){ return 0 ; }

};

App2 appobj;

CCSharpThread<App2> thread(appobj,App3::InvalidThreadBody);  //试图调用appobj-> InvalidThreadBody方法

产生编译错误error C2664: '__thiscall CCSharpThread<class App2>::CCSharpThread<class App2>(class App2 &,unsigned long (__thiscall App2::*)(void))' : cannot convert parameter 2 from 'unsigne d long (__thiscall App3::*)(void)' to 'unsigned long (__thiscall App2::*)(void)'这样就防止了开发人员用App2对象去调用App3的成员方法。

6.      总结

本文通过封装C界面的线程API实现了CJThread/IRunable模型,并进一步建立了CCSharpThread模型。可以看到这两个模型的实现非常简单,对资源的要求也非常低,这意味它们是很高效的,这使它们成为轻量级的C++多线程应用非常好的选择。实际上应指出,CCSharpThread的“将任意一个原型符合要求的公有非静态类成员方法作为线程方法”的能力,以及它类型安全的特点,使得它比MFCVCL等类库提供的线程模型更强大。

0 0

相关博文

我的热门文章

img
取 消
img