2

在 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() 将被内联,因为没有理由不...

4

3 回答 3

1

正如Useless 在他的回答中提到的,严格来说,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了模板化的非继承设计。看看boost::threadC++11 是否不是一个选项。

于 2013-01-19T09:44:54.730 回答
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;
};
于 2013-01-18T15:08:49.893 回答
1

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

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

不,这取决于您的型号。

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

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

于 2013-01-18T15:47:56.837 回答