关于多线程:pthread用于C ++中类的成员函数 – 为什么不用模板?

pthread for a member function of class in C++ - why not template?

使用C++中具有类成员函数的p螺纹的一种"典型"方法是使用继承(如这里建议的HTTPS://StaskOfFult.COM/A/1151615/157334)。但是为什么不这样呢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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;
};

可以这样使用:

1
2
3
4
5
6
7
8
9
class SomeClassWithThread
{
public:
    int initialize() { return _thread.create(this); };

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

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

此外,我认为这更有意义,因为一个典型的对象有线程(组合),而不是线程(继承),对吗?(;

与继承方法相比,我在任何地方都没有看到这种设计有什么缺陷吗?对于每个实例,您都会得到一个Trampoline()的副本,但这与继承版本中的虚拟函数调用没有太大的区别…我希望create()和gethandle()是内联的,因为没有理由不…


正如他回答中提到的无用,严格地说,pthread库调用的线程函数需要是extern"C"。虽然静态成员函数在几乎所有情况下都有效,但从语言律师的角度来看,至少在一个现实生活中,它是不正确的。有关详细信息,请参阅https://stackoverflow.com/a/2068048/12711。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#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提供了EDCOX1,6,这是一个模板化的非继承性设计。并看EDCOX1 7,如果C++ 11不是一个选项。


您的Thread::_trampoline方法没有extern"C"链接,因此,尽管这在实践中可能有效,但并不正确。因为您不能给模板函数提供正确的链接,所以除非您允许宏,否则没有一种简单的方法来自动执行此操作。

Moreover I think it makes more sense, because a typical object rather HAS-A thread (composition), than IS-A thread (inheritance), right? (;

不,这取决于你的型号。

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

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


这解决了"this"地址的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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;
};

更新用法:

1
2
3
4
5
6
7
8
9
class SomeClassWithThread
{
public:
    int initialize() { return _thread.create(this); };

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