【问题标题】:c++ thread local counter implementc++线程本地计数器实现
【发布时间】:2022-01-06 03:05:22
【问题描述】:

我想在多线程进程中实现一个高性能计数器,像这样,每个线程都有一个名为“t_counter”的线程本地计数器来计算查询(incr 1/query),并且在“定时器线程”中有一个名为“global_counter”,我想要的是每秒,global_counter 将获取每个 t_counter(s) 并将它们添加到 global_counter,但我不知道如何在“计时器线程”中获取每个 t_counter 值。另外,线程局部值将放在主内存中的哪个部分? .data 或 heap 或其他?如何动态分配内存大小(可能有 10 个线程或 100 个线程)? x86-64 是否使用段寄存器存储这样的值?

【问题讨论】:

  • "但是我不知道如何获取'timer thread'中的每个t_counter值",我存储t_counter指针来解决问题
  • 我不会这样设计的。最好让所有线程每隔一段时间将其counter 值作为消息发送到管理类(消息传递)。最好不要使用共享内存。

标签: c++ thread-local


【解决方案1】:

从你的第二个问题开始,你可以找到所有的规范here

总结一下,线程局部变量是在 .tdata / .tbss 中定义的。这些有点类似于 .data,但访问它们是不同的。这些部分按线程复制。实际变量偏移量是在运行时计算的。

变量由 .tdata 中的偏移量标识。说到 x86_64,它将使用 FS 段寄存器来查找 TCB(线程控制块),使用存储在那里的数据结构,它将定位变量所在的线程本地存储。请注意,如果可能,所有分配都是惰性完成的。

现在,关于您的第一个问题 - 我不知道有一种方法可以仅列出来自另一个线程的所有线程局部变量,我怀疑它是否可用。

但是,线程可以获取指向线程变量的指针,并将其传递给另一个线程。所以你可能需要一些注册机制。

每个新线程都会将自己注册到某个主存储,然后在终止时取消注册。注册和注销由您自己负责。

示意图如下:

thread_local int counter = 0;
std::map<std::thread::id, int *> regs;

void register() {
    // Take some lock here or other synchronization, maybe RW lock
    regs[std::this_thread::get_id()] = &counter;
}
void unregister() {
    // Again, some lock or other synchronization
    regs.erase(std::this_thread::get_id());
}

void thread_main() {
    register();
    counter++;
    unregister();
}

void get_sum() {
    // Again, some lock, maybe only read lock
    return std::accumulate(regs.begin(), regs.end(), 0,
        [](int previous, const auto& element)
        { return previous + *element.second; });
}

【讨论】:

  • 但是这样安全吗?您只锁定注册一个指向计数器的指针。但增加计数器并没有锁定。这显然是在 OPs 代码中定期完成的。这种方式调用get_sum 可能会导致一些读写数据竞争条件。锁定counters 也不是很好,因为这会在累积时停止所有线程。
  • @JHBonarius 正确,每个线程/每个 cpu 计数器模式擅长在系统处于活动状态时给出 近似 总和,或 准确 总和如果您确保系统当时处于非活动状态。但请注意:测量的总和始终 可能仍然存在。这种模式有其应用,在内核模式下特别有用,但它绝对不适合所有人。不过它是一个可用的工具。
  • 是的,但我想说:读写count 不是原子的。因此,由于它没有受到保护,可能会发生读写。那是 C++ 中的 UB,因此你不应该那样做!!编辑:例如read this
  • @JHBonarius 理论上你是对的,一般来说 C++,CPU 架构。实际上,说到 x86 或 arm,只有当计数器正好位于两个高速缓存行的边界上时,这才可能成为问题。这对于一个简单的全局变量来说是不正确的。在一些具有一些奇怪架构的微控制器上,或者在一些自定义编译器上,这可能是一个问题,你是对的。使用时应牢记这一点。就是这样。
  • UB 是 UB,你应该避免。多年来生产中的许多大问题都是由人们在推断 UB 时做出假设引起的。就像我之前链接的答案所说:“使用atomic&lt;T&gt; 类型。它面向性能,甚至可能在支持它的平台上无锁。”。或者完全使用不同的机制,比如消息传递。
猜你喜欢
  • 2010-12-02
  • 2014-06-01
  • 2011-04-13
  • 2011-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多