使用c ++类成员函数作为c回调函数,线程安全版本

时间:2016-12-17 13:02:26

标签: c++ multithreading

The original question,对如何进行非线程安全版本有了很好的答案。

以下是我尝试稍微修改以开始工作的代码:

#include <stdio.h>
#include <functional>
#include <thread>

void register_with_library(int (*func)(int *k, int *e)) {
   int x = 0, y = 1;
   int o = func(&x, &y);
}

typedef int (*callback_t)(int*,int*);

class A {
  template <typename T>
  struct Callback;

  template <typename Ret, typename... Params>
  struct Callback<Ret(Params...)> {
     template <typename... Args>
     thread_local static Ret callback(Args... args) {
        func(args...);
     }
     thread_local static std::function<Ret(Params...)> func;
  };
   public:
      A();
      ~A();
      int e(int *k, int *j);
    private:
      callback_t func;
};

template <typename Ret, typename... Params>
thread_local std::function<Ret(Params...)> A::Callback<Ret(Params...)>::func;

A::A() {
   Callback<int(int*,int*)>::func = std::bind(&A::e, this, std::placeholders::_1, std::placeholders::_2);
   printf("1.  C callback function ptr %p, C++ template function ptr %p Object ptr %p \n",func, Callback<int(int*,int*)>::func,  this) ;
   func = static_cast<callback_t>(Callback<int(int*,int*)>::callback);
   printf("2.  C callback function ptr %p\n",func) ;
   register_with_library(func);
}

int A::e(int *k, int *j) {
   return *k - *j;
}

A::~A() { }

int main() {
   std::thread t1 = std::thread { [](){ A a;}};
   std::thread t2 = std::thread { [](){ A a;}};

   t1.join();
   t2.join();
}

结果是

function ptr 0x400eef
function ptr 0x400eef

考虑到我有多个线程创建不同的对象,如何正确地为每个新对象创建新的回调?

编辑:

正如e.jahandar所建议的,使用thread_local可以部分解决问题(仅当每个线程创建了1个对象时)。由于这个原因,Callback<int(int*,int*)>::func是基于线程分配的。虽然问题仍存在于Callback<int(int*,int*)>::callback

没有thread_local:

1. C callback function ptr 0x403148, C++ template function ptr 0x609180 Object ptr 0x7ff9ac9f3e60 
2. C callback function ptr 0x403673
1. C callback function ptr 0x4031a6, C++ template function ptr 0x609180 Object ptr 0x7ff9ad1f4e60 
2. C callback function ptr 0x403673

使用thread_local:

1. C callback function ptr 0x403230, C++ template function ptr 0x7fc1ecc756d0 Object ptr 0x7fc1ecc74e20 
2. C callback function ptr 0x403701
1. C callback function ptr 0x4031d2, C++ template function ptr 0x7fc1ec4746d0 Object ptr 0x7fc1ec473e20 
2. C callback function ptr 0x403701

2 个答案:

答案 0 :(得分:1)

如果每个线程只需要一个特定对象的实例,则可以使用带有__thread存储类的对象指针的全局变量,__ thread读取全局变量对该线程的唯一性。

使用带静态成员的单调类进行回调是另一种解决方案,就像之前的解决方案一样,您可以使用__thread为每个线程分离monothonic类实例。

另外请注意,__thread不是标准的东西

修改

这是一个例子

class.h

class someClass{
     private:
         someMethod(){ ... }
}

class.cpp

__thread void * objectPointer;

void initialize(){
    someClass * classPtr = new someClass();
    objectPointer = (void *) classPtr;
}

void * callbackFunction(void * args){
    someClass * obj = objectPointer;
    obj->someMethod();
}

答案 1 :(得分:0)

使用C语言调用C ++函数的唯一符合标准的方法是使用C链接声明它。

extern "C" int my_callback_wrapper (A*, int*, int*);

my_callback既不是模板,也不是成员函数(甚至是静态的)。

除此之外的任何事情都是未定义的行为。

为了符合标准,您必须使用单独的回调包装器手动包装每个单独的成员函数。此解决方案自然是线程安全的,因为它不使用任何全局或静态数据。

当然,它需要C代码获取A对象的指针,并在正确的时刻将右指针传递给回调。从C库隐藏A的实例相当于在一些静态/全局存储中存储A,这意味着每个回调只能有一个。如果您使用的是C ++ 11,则还可以指定thread_local存储并为每个线程创建一个对象。

extern "C" int my_callback_wrapper (int* x, int* y);
thread_local A* aptr = nullptr;
thread_local int (A::*fptr)(int*, int*) = nullptr;

void register_cxx_callback (A* a, int (A::*f)(int*, int*))
{
    if (aptr != nullptr || fptr != nullptr)
        fatal_error ("Trying to overwrite the callback!");
    aptr = a;
    fptr = f;
    register_with_library(my_callback_wrapper);
}

extern "C" int my_callback_wrapper (int* x, int* y)
{
    if (aptr == nullptr || fptr == nullptr)
        fatal_error ("Callback is called but the object is not registered!");
    printf ("aptr is %p\n", (void*)aptr);
    return (aptr->*fptr)(x, y);
}

一个完整的工作示例是here

只要C库没有尝试在线程之间传递已注册的回调,此解决方案也是线程安全的。据我所知,它也符合标准。

没有模板,因为没有C链接模板。可以将静态/线程局部数据包含在一个类中,但是我不会在只有静态数据的类中看到很多点。

无法为每个线程移植注册任意数量的对象。最后,你必须提供一个C函数指针,并且C函数不能可移植地隐藏任意数据(IOW你不能在符合标准的C中构建闭包)。如果您对非便携式解决方案感兴趣,可以使用library that does that