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

两种面向对象的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++){