两种面向对象的C++线程模型如何?
两种面向对象的C++线程模型
摘要:本文首先分析对比了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
} //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++){
本文地址:http://www.45fan.com/dnjc/71704.html