2016-12-17 145 views
1

The original question,對於如何執行非線程安全版本得到了很好的答案。使用C++類成員函數作爲c回調函數,線程安全版本

下面是代碼,我已經試過稍微修改去上班:

#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(); 
} 

結果是

​​

一個怎樣使這個正常工作,創造新的回調對於每個新對象,考慮到我有多個線程創建不同的對象?

編輯:

如建議通過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 
+0

除了調用未定義的行爲(假設實際代碼中的register_with_library是用C語言編寫的C庫的成員),此解決方案不允許您註冊兩個相同類型的回調。它只覆蓋上次註冊回調時唯一的靜態變量,並默默丟棄前一個。 –

+0

@ n.m是的,這正是問題所在。但有沒有解決方案? –

+0

看到我的答案。如果您不耐煩,請跳至最後一段。 –

回答

1

如果每個線程只需要特定對象的一個​​實例,您可以使用與__thread存儲類對象指針的全局變量,__thread使全局變量對於該線程是唯一的。

使用單調類與靜態成員進行回調是另一種解決方案,與以前的解決方案一樣,可以使用__thread爲每個線程分離單音類實例。

也知道,__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(); 
} 
+0

我試過在這裏實現它,但我得到:非局部變量'A ::回調 :: func'聲明'__thread'需要動態初始化。我是否正確使用它? –

+0

顯示代碼,但不能使用__thread作爲非靜態類成員 –

+0

我試過使用thread_local並將其添加到3行:'thread_local static Ret callback(Args ... args)','thread_local static std ::函數 func;'和'thread_local std :: function A :: Callback :: func;' –

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

+0

我認爲這可能對我來說已經足夠好了,因爲我不需要每個線程有多個對象(儘管這也有興趣查看解決方案)。當我通過I來實現它時,即使我在aptr和fptr賦值後立即調用my_callback_wrapper,它們仍然沒有被定義。爲了避免衝突,我必須將這些包含在標題中的命名空間大括號中,可能是這樣嗎? –

+0

我不確定你的意思,你能展示代碼嗎? –

+0

抱歉,對不起,這是一個愚蠢的錯誤。我已經調用my_callback_wrapper來測試它在我用C庫註冊之前的工作,並且它工作正常。但是,一旦C庫直接撥打電話,就會收到段錯誤。 –