【问题标题】:pthread for a member function of class in C++ - why not template?c++ 中类的成员函数的 pthread - 为什么不是模板?
【发布时间】:2013-01-18 12:36:40
【问题描述】:

在 C++ 中将 pthread 与类的成员函数一起使用的“典型”方法是使用继承(如此处建议的 https://stackoverflow.com/a/1151615/157344 )。但为什么不这样呢:

#include <pthread.h>

template <class T, void * (T::*thread)()>
class Thread
{
public:
    int create(T *that) { return pthread_create(&_handle, nullptr, _trampoline, that); };
    pthread_t getHandle() const { return _handle; };

private:
    static void * _trampoline(void *that) { return (static_cast<T *>(that)->*thread)(); };

    pthread_t _handle;
};

可以这样使用:

class SomeClassWithThread
{
public:
    int initialize() { return _thread.create(this); };

private:
    void * _threadFunction();
    Thread<SomeClassWithThread, &SomeClassWithThread::_threadFunction> _thread;
};

它的优点是不使用虚拟功能,因此不使用 vtable 并且使用的 RAM 更少(我正在为 MCU 开发它,而不是为 PC 开发,因此 RAM 的使用很重要)。它也不需要虚拟析构函数。

此外,我认为它更有意义,因为典型的对象而不是 HAS-A 线程(组合),而不是 IS-A 线程(继承),对吧? (;

与继承方法相比,这种设计是否有任何缺陷,因为我在任何地方都没有看到它提出的建议?您肯定会为每个实例化获取 _trampoline() 的副本,但这与继承版本中的虚函数调用没有太大区别......我希望 create() 和 getHandle() 将被内联,因为没有理由不...

【问题讨论】:

  • 根据我的经验,您链接到的问题的公认答案比基于继承的版本更典型。
  • @molbdnilo - 当然,但这不是一种“通用”或“自动”的方式来做到这一点 - 它只是为你想用 pthread 增强的每个类手动提供这样的 trampoline() .无论如何,我认为我的模板方法更接近于这样的版本——它只是一个非常漂亮的宏,可以做同样的事情(;或者至少我认为确实如此——我是这些领域的新手......
  • 您可能希望向 Thread 类添加一个析构函数,以便在该类被销毁时停止线程。 Nitpick:在变量名中使用前导下划线是个坏主意;这些是为编译器保留的。
  • 我认为这里有一个错误:您在 create() 中使用的“this”是帮助程序类的地址,而不是您要用作客户端的类的地址。你的例子是偶然的,因为你的助手类是第一个成员。
  • @SimonElliott,这些名称不是保留的。 _This 是保留名称,__this 也是,但 _this 不在全局命名空间中(并且这些名称都不在全局命名空间中)

标签: c++ multithreading templates pthreads


【解决方案1】:

这解决了“this”地址的问题

#include <pthread.h>

template <class T, void * (T::*thread)()>
class Thread
{
public:
    int create(T* passedThis) { return pthread_create(&_handle, nullptr, _trampoline, passedThis); };
    pthread_t getHandle() const { return _handle; };

private:
    static void * _trampoline(void *that) { return (static_cast<T*>(that)->*thread)(); };

    pthread_t _handle;
};

更新用法:

class SomeClassWithThread
{
public:
    int initialize() { return _thread.create(this); };

private:
    void * _threadFunction();
    Thread<SomeClassWithThread, &SomeClassWithThread::_threadFunction> _thread;
};

【讨论】:

  • 我刚刚将其更改为相同的语义 - 很好!实际上,当线程对象首先出现时它正在工作,如果不是(;
【解决方案2】:

您的Thread::_trampoline 方法没有extern "C" 链接,因此,虽然这在实践中可能有效,但它是不正确的。由于您无法为模板函数提供正确的链接,因此除非您允许宏,否则没有简单的方法可以自动执行此操作。

此外,我认为它更有意义,因为典型的对象而不是 HAS-A 线程(组合),而不是 IS-A 线程(继承),对吧? (;

不,这取决于您的型号。

  • 活动对象经常紧密绑定到单个线程(因此您可以考虑比一个实例 IS-A 并发执行进程)
  • 对于任务队列(或线程池),task 通常是对象,而线程与一些调度逻辑相关联(队列/池本身当然也可能是对象,但这似乎不是您的建议)

老实说,如果您要创建这么多不同的顶级线程函数,以至于担心 vtable 占用的内存,我怀疑您的设计首先是错误的。

【讨论】:

  • 关于 _trampoline() 不是 extern "C" - 为什么会改变任何东西,调用约定是已知的,我只是不知道这会产生什么不同......至于你的最后一句话关于设计错误,这似乎不是一个建设性的评论 - 如果您不需要做任何特殊的事情来实现这一点,那么节省几个字节并没有错,并且模板的解决方案与继承的解决方案一样好。 .. 至于中间的东西(组合与继承),您可以轻松地使用一个这样的线程创建一个对象并继承它 - 瞧 (;
  • 如果您知道静态 C++ 链接函数与 ABI 中的 C 链接函数兼容,那对您有好处。我不知道您的平台保证什么,所以我只是指出它不可移植。其余的只有在模板解决方案正确的情况下才会出现,我们已经确定了这一点我一般不知道。至于设计,这可能是一个公平的评论......我只是没有足够的背景来建议更好的方法。
  • 不要误会我的意思——我不是说你弄错了,但我只是从未听说调用静态成员函数与调用普通 C 函数有什么不同(不包括名称修饰,但确实如此在这里没关系),所以我只是好奇 - 你永远不会停止学习(; 作为旁注 - 继承方法也使用静态成员函数作为蹦床,所以这是两种方法的“缺陷”。
  • 是的,但是如果您使用带有继承方法的 ABC,您可以轻松地使用单个非模板化、非成员 extern "C" trampoline 函数从 void * 进行初始转换并调用虚拟的。
【解决方案3】:

作为Useless mentioned in his answer,严格来说pthread库调用的线程函数需要是extern "C"。虽然静态成员函数几乎适用于所有情况,但从语言律师的角度和至少一种现实生活的情况来看,它是不正确的。详情请见https://stackoverflow.com/a/2068048/12711

但是,您可以让extern "C" 函数提供pthread 库和您的类模板之间的接口,但这似乎需要一点点开销:

#include <pthread.h>

struct trampoline_ctx
{
    void* (*trampoline)(void*);
    void* obj;
};

extern "C"
void* trampoline_c(void* ctx)
{
    struct trampoline_ctx* t = static_cast<struct trampoline_ctx*>(ctx);

    return (t->trampoline)(t->obj);
}


template <class T, void * (T::*thread)()>
class Thread
{
public:
    int create(T *that) { 
        ctx.trampoline = _trampoline;
        ctx.obj = that;
        return pthread_create(&_handle, nullptr, trampoline_c, &ctx); 
    };
    pthread_t getHandle() const { return _handle; };

private:
    static void * _trampoline(void *that) { return (static_cast<T *>(that)->*thread)(); };

    pthread_t _handle;
    struct trampoline_ctx ctx;
};

我同意在大多数情况下,组合而不是继承可能是更好的线程模型。

当然,请记住,C++11 提供了std::thread,这是一个模板化的非继承设计。如果不能选择 C++11,请参阅 boost::thread

【讨论】:

  • std::thread 解决方案对我不起作用——首先,我使用的 RTOS 使得无法使用工具链中的 stdlibc++(是的,我也同意这很愚蠢),其次在所有 C++11 设计中,对于我的 24kB 或 RAM 和 24MHz 芯片来说,这可能是一个很大的杀伤力,第三——这个实现依赖于操作系统,这里没有真正的操作系统(;至于你的解决方案——使用动态分配这个开销可能是临时的(线程启动时不需要所有 3 个指针),但这会给图片带来堆 (; 总是有一个权衡 (;
  • 虽然根据 c++ 标准的说法,我意识到上述陈述是正确的。这是为大多数编译器混合 c 和 C++ 时不使用 extern "C" 的少数情况之一,尤其是 gnu 兼容编译器(g++、clang++)。我认为这样做的原因是大多数 C++ 编译器在编译时找到函数名称并将指针值插入。我认为问题在于某些编译器实际上将名称传递给 pthread_create 导致崩溃,因为该名称不存在。如果有人对此有更多详细信息,希望听到
猜你喜欢
  • 1970-01-01
  • 2020-10-03
  • 2018-01-28
  • 2019-02-10
  • 1970-01-01
  • 2017-02-03
  • 2019-05-20
相关资源
最近更新 更多