如何为每个实例提供非静态线程局部变量

时间:2017-01-23 07:08:36

标签: c++ multithreading

问题本身:

class B{/*...*/};
class A {
    /* members */
    NON-static thread_local B var; // and a thread_local variable here
    ret_t method(/* args */);
};

我希望var独立存在于每个线程和每个实例中。

更大(完整)的问题:

A的实例跨线程共享。 B是调用A::method所必需的一些资源,并且它必须是独立的线程以避免竞争条件(即A::method必须具有"写访问权限#34; {{1 }})。对于var的不同实例,相应的B会有所不同。

我提出的一个不完全令人满意的方法是使用一些容器(比如A)来存储每个实例的每个std::unordered_map<THREAD_IDENTIFIER_TYPE, B>对应的每个var。但是,这既不限制跨线程访问thread也不会阻止整个容器被修改。 (因此,要求开发人员要小心,以便编写安全的代码。)

我在SO上看过一些关于java ThreadLocal关键字(?)的帖子,但它们似乎都没有提供真正有用的想法。有什么建议吗?

3 个答案:

答案 0 :(得分:6)

您无法声明thread_local的非静态成员。见cppreference。特别是:

  

thread_local关键字仅允许在命名空间范围内声明的对象,在块范围内声明的对象和静态数据成员。

如果你不想使用pthreads(在Windows上很棘手),一些容器是你唯一的选择。

一种选择是std::unordered_map<THREAD_IDENTIFIER_TYPE, B>的变体。 (您可以编写一个类来包装它并使用互斥锁保护地图。)

另一个最具吸引力的选项是thread_local A静态成员,将A*映射到B将避免任何锁定。

class A {
    static thread_local std::unordered_map<A*, B> s_B;
    ....
};

用法:

void A::foo() {
    B& b = s_B[this];  // B needs to be default constructable.
    ...

问题是您需要某种方法从s_B地图中删除元素。如果A对象实际上被锁定到特定线程,或者如果你有某种方法可以在另一个线程上调用函数,那么这不是太大的问题 - 但它也不是完全无关紧要的。 (您可能会发现使用A的唯一标识符更安全,这是一个递增的64位计数器 - 这样,在销毁A对象和消息之间重复使用标识符的风险要小得多从正在处理的所有地图中删除B。)

答案 1 :(得分:1)

如果可用,您可以使用pthread - 函数pthread_getspecificpthread_setspecific作为getter和setter用于此目的:

#include <pthread.h>

class A {
private:
#define varKey 100L

public:

    int getVar() {
        void *mem = pthread_getspecific(varKey);
        if(mem)
            return *((int*)mem);
        else
            return 0;
    }

    void setVar(int val) {
        void *mem = malloc(sizeof(int));
        *((int*)mem)=val;
        pthread_setspecific(varKey, mem);
    }

    ~A() {
        void *mem = pthread_getspecific(varKey);
        if (mem)
            free(mem);
    }

};

答案 2 :(得分:1)

如果您愿意使用tbb(即使是英特尔免费),您可以使用他们的tbb::enumerable_thread_specific<T>模板类(基本上类似于std::unordered_map<thread_id,T>免费锁定,我明白了。由于A在线程之间共享,因此每个A实例都需要一个这样的容器,但最好将B声明为嵌套类型。例如

class A
{
  struct B
  {
    B(const A*);
    void call(/* args */);
  };
  tbb::enumerable_thread_specific<B> _B ([&]()->B { return {this}; } );
  void method(/* args */)
  {
    _B.local().call(/* args */);   // lazily creates thread local B if required.
  }
  /* ... */
};