摘要:本文首先分析对比了Java、C#的线程模型,然后在C++中实现了类似Java的轻量级线程模型 CJThread/IRunable,并在此基础上利用模板和operator->*实现类似C#的线程模型CCSharpThread。本文重点推荐CCSharpThread模型,它不仅是类型安全的,还能将任意一个原型为DWORD()的公有非静态类成员方法作为线程方法。
 
关键词:C++;模板;operator->*;线程;线程模型
 
       面向对象的线程模型使开发人员能以面向对象的方法来看待线程,并以面向对象的方法实现线程的应用逻辑;它包含两个要素,封装线程逻辑(如线程的创建、销毁、管理等)的线程对象和实现线程应用逻辑的线程方法。C++本身没有提供面向对象的线程模型。目前常用的Win32 Thread和Posix Thread都只提供C界面的API(简称线程API),并且只能用普通C函数作为线程方法。C++的非静态类成员方法由于包含一个隐含的this参数而不能用作线程方法,使得C++程序员开发轻量级(指不采用MFC、VCL等类库)多线程应用时不能利用面向对象的方法看待线程。本文首先分析对比了Java、C#的线程模型,然后在C++中实现了类似Java的轻量级线程模型 CJThread/IRunable,并在此基础上利用模板和operator->*实现类似C#的线程模型CCSharpThread。本文重点推荐CCSharpThread模型,因为它不仅是类型安全的,还能将任意一个原型为DWORD()的公有非静态类成员方法作为线程方法。本文中线程方法是指实现线程应用逻辑的函数。在C++中既可以是全局函数,也可以是类成员方法。
1.      Java、C#线程模型分析对比
       Java、C#都提供了面向对象的线程模型。它们都抽象出了线程对象,而开发人员在某个类的成员方法中实现线程应用逻辑。通过分离线程对象和线程方法,简化了线程应用逻辑的开发。
在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线程模型中只能在Runable或Thread的子类中实现线程方法(即子类的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派生CJThread。CJThread实现空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年第9期76页上的《为智能指针实现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/IRunable、CCSharpThread两个线程模型的用法,同时体会一下CCSharpThread模型的强大。假设在某个应用中,需要启动两个线程,一个线程将一个变量从0每次加1增加到3,然后退出,另外一个线程监视这个变量,当这个变量为3时就退出。下面分别用CJThread/IRunable、CCSharpThread来实现这个应用程序。
例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的“将任意一个原型符合要求的公有非静态类成员方法作为线程方法”的能力,以及它类型安全的特点,使得它比MFC、VCL等类库提供的线程模型更强大。