在C ++中创建类似runnable类的问题

时间:2011-10-07 09:54:33

标签: c++ multithreading

我一直在尝试创建一个可运行的类,以便像java-folks那样连接多线程类。但我似乎无法使用_beginthread与虚函数运行。 我收到以下错误:

' beginthread':无法转换参数1 从'void( _cdecl Runnable :: *)(void *)'到'void(__ cdecl *)(void *)'

#include "CriticalSection.h"
#include <stdio.h>
#include <conio.h>
#include <process.h>

class Runnable{
private:
    Runnable() { _beginthread(&Runnable::Run,0,(void*)0); }
    ~Runnable();
    virtual void __cdecl Run ( void* ) = 0;
};

int main(){
    //CriticalSection crt;
    //ErrorHandler err;
    //LockSection lc(&crt,&err);

    while(!kbhit());
    return 0;
}

4 个答案:

答案 0 :(得分:5)

实现多线程库有两种方法,它们都在Java中使用,C ++的变体略有不同。既然你似乎了解Java,我将讨论那里的方法:

从线程派生

在这种方法中,有一个表示线程的基类,您从该基类继承并实现一个在线程启动时将被调用的方法。基本线程类提供对线程的控制,派生类实现run(或等效)方法。基本线程类不能启动实际线程,但让用户代码通过成员方法start启动它。原因是如果基本线程类启动了线程,则新生成的线程可能会在创建完整对象之前尝试调用重写的方法。这有一个令人讨厌的副作用,就是在C ++中调用基本线程类中的覆盖器,或者在Java中调用尚未初始化的对象上的run方法的最终覆盖。 - 两种情况都不安全。

Runnable接口

为了减少导致未定义行为的可能性以及分离职责,第二种方法将线程控制与要在新生成的线程中运行的代码的实现区分开来。创建了一个线程类,但并不打算用作基类。而是为Runnable个对象提供了一个接口。用户代码将从该接口派生并将对象传递给线程类。设计确保无法完成先前的错误,因为在将Runnable传递给线程之前必须完全创建{<1}}。

Runnable接口的现代C ++方法

新的标准线程库(和boost线程)提供了类似于Runnable接口的方法,但存在一些差异。首先,用户代码不需要填充具体的接口,但实际上可以告诉线程库要运行类的哪个方法以及使用什么参数。这是通过在线程类的构造函数中应用类型擦除来实现的,其中用户参数的确切类型被擦除以供稍后在线程内使用。这与Runnable版本具有相同的优点 - 确保在生成线程之前完全创建对象。同时,它消除了必须实现精确接口的要求,这意味着您将能够使用自由函数或类的成员函数以及要调用该成员函数的实例,或实现operator() ...

的仿函数

如果您要使用C ++进行编码,我建议您使用最后一种方法,因为这对用户代码来说是最灵活的,同时它比您尝试的更安全。也就是说,不要重新发明轮子,有很好的思想C ++线程库(C ++ 11,如果你有它,如果你没有提升 - 或Poco,或ACE ...)将避免许多陷阱。

要记住的一个非常重要的一点是,无论您遵循什么方法,都必须确保新线程不会尝试在未完全创建的对象中调用虚函数,因为这将导致未定义的行为。 / p>

答案 1 :(得分:3)

即使您编写的内容按预期编译,也不会正常运行。您正在构造函数中启动一个线程,该线程将在this上调用纯虚函数。但是构造函数运行时的虚拟调度不会分派给运行构造函数的类派生的类。

如果编译了该代码,您将遇到竞争条件:虚拟调用可能在Runnable的构造函数结束之前,或者在子类构造函数运行时,或者在它们全部运行之后发生。其中每一个都有不同的结果,第一个可能会崩溃。

答案 2 :(得分:2)

您需要将该函数传递给_beginthreadstatic

我建议你阅读this article by Herb Sutter并尝试实现Active Object Pattern。

答案 3 :(得分:1)

使用Boost.Thread或C ++ 11 std::thread。指向成员的指针与指向函数的普通指针不同,因此您无法将它们传递给C库(它们需要this指针存在,并且库无法处理它,因为C中没有这样的东西。< / p>