【问题标题】:How can I have non-static thread-local variable for each instance如何为每个实例提供非静态线程局部变量
【发布时间】:2017-01-23 07:08:36
【问题描述】:

问题本身:

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 必须对var 具有“写访问权”)。而A的不同实例对应的B也不同。

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


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

【问题讨论】:

    标签: c++ multithreading


    【解决方案1】:

    您不能将非静态成员声明为 thread_local。见cppreference。特别是:

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

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

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

    另一个最初吸引人的选项是Athread_local 静态成员,它将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 对象和删除 @ 的消息之间重复使用标识符的风险要小得多987654335@ 来自所有正在处理的地图。)

    【讨论】:

    • 这令人沮丧...然后我宁愿在线程中为每个实例使用一个容器,因为它不会被共享,因此不需要互斥锁。
    • "问题是您需要某种方法从 s_B 映射中删除元素。如果 A 对象实际上被锁定到特定线程,或者如果您有某种方法可以在另一个线程上调用函数——但这也不是完全无关紧要的。”你能详细说明一下吗?为什么从地图中删除元素可能很难,我仍然感到困惑。
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      如果可用,您可以使用 pthread-functions 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);
          }
      
      };
      

      【讨论】:

      • 感谢您的想法!我对 pthread 特定的 API 知之甚少,但这感觉就像将容器保持在线程级别,并将 var 存储在其中,不是吗?
      • 对; API 会为您维护这个容器。
      • @Richard Hodges;同意,这就是为什么前两个词是“可用的”,而tread_local 不适用于非静态成员;但是,在pthread 可用的地方,它是众所周知的并且得到了很好的应用。
      • @StephanLechner thread_local 确实适用于非静态成员,如果你保留一个线程特定的映射,这可以完全在可移植的 c++ 中完成。
      • pthread_getspecificpthread_setspecific 采用从 pthread_key_create 获得的 pthread_key_t,而不是 long。这个答案的用法非常不正确和不安全;它的工作方式似乎与将随机整数转换为指针并将其用作地址的方式相同,但实际上并没有。
      猜你喜欢
      • 2015-10-15
      • 2012-08-16
      • 2015-11-09
      • 2011-11-28
      • 2014-02-03
      • 1970-01-01
      • 2018-08-05
      • 1970-01-01
      • 2013-11-12
      相关资源
      最近更新 更多