【问题标题】:lookup tables in C++ 11 with multithreadingC++ 11 中的多线程查找表
【发布时间】:2013-06-30 20:33:13
【问题描述】:

我在多线程 C++11 软件中有 2 种类似情况:

  • 我在方法声明中用作查找表的数组
  • 一个数组,我用作在方法外部声明的查找表,并且通过引用或指针被不同的多个方法使用。

现在,如果我们暂时忘记这个 LUT,而只考虑 C++11 和泛型方法的多线程方法,那么就存储持续时间而言,这种方法最合适的限定词可能是 thread_local

这样,如果我将 foo()thread_local 的方法提供给 3 个线程,我基本上最终会为每个线程提供 3 个 foo() 实例,这一举措“解决”了 foo() 被共享和在 3 个不同的线程之间访问,避免缓存丢失,但我的 foo() 基本上有 3 种可能的不同行为,例如,如果我在 foo() 中实现了相同的 PRNG,并且我提供了一个与时间相关的种子好和高分辨率,我可能会在每个线程中得到 3 个不同的结果,并且在一致性方面真的一团糟。

但是假设我对 thread_local 的工作方式很好,我如何写下我需要保持 LUT 始终准备好并为我的方法缓存的事实?

我读过一些关于宽松或不那么宽松的内存模型的文章,但在 C++11 中,我从未见过可以注入数组/LUT 缓存的关键字或实际应用程序。

我使用的是 x86 或 ARM。

我可能需要与volatile 基本相反的东西。

【问题讨论】:

  • 当然,最简单的事情就是不分享。它们很大吗?如果你需要分享,TBB 有几个实现。如果你用谷歌搜索“并发哈希表 C++”,有很多 answers 相关的:)。如果您使用 VS,您可以使用 concurrent_unordered_map
  • @DmitryLedentsov 感谢您的提示,但它可能不适合我,我想用纯 C++11 编写它以实现可移植性和更好的投资,我也想学习如何做到这一点靠我自己,是的,我的 LUT 可能很大,想象一下 LUT 包含一个大调色板或一组类似的值。我的问题不是让这个数组成为一个并发数据结构,而是如何让它缓存。
  • 我明白了。您的问题的性质与功能本身无关。我首先会选择标准库:concurrent_unordered_map,只有在我真的需要时才开始构建自己的库。所以,也许,重新考虑架构会减少对特殊并发查找表的需求
  • thread_local 只能应用于变量或静态数据成员——谈论“thread_local 的方法”是没有意义的

标签: multithreading c++11 thread-local


【解决方案1】:

如果 LUT 是只读的,因此您可以在没有锁定的情况下共享它们,您应该只使用其中一个(即声明它们 static)。

线程没有自己的缓存。但即使他们这样做了(核心通常有自己的 L1 缓存,并且您可能能够将线程锁定到核心),两个不同的线程缓存同一内存结构的不同部分也不会有问题。

“线程本地存储”并不意味着内存在物理上与线程相关联。相反,这是一种让相同名称在每个线程中引用不同对象的方法。如果给定地址,它绝不会限制任何线程访问该对象的能力。

【讨论】:

  • 所以在 C++ 中我可以制作一些东西 volatile 但我不能显式缓存值?最后,您建议使用staticconst 修改存储持续时间(因为在这种情况下const 会产生相同的效果)并将此LUT 与指针或引用一起使用?
  • @user2485710: volatile 不会“使某些东西变得易变”。事物要么不稳定,要么不稳定;你必须知道。 (例如,硬件寄存器是易失性的。)volatile 告诉编译器它是易失性的。同样,存储持续时间与编译器生成代码的方式有关;这不是存储固有的东西。
  • @user2485710:不,您不能显式缓存值,即使可以,您也不需要将值放在内存中的特定位置。
  • 是的,但是当您的程序中有一个业务逻辑使用“一次性”值或一组值时,您应该使用volatile,例如,如果您知道这值正在改变你通知编译器这个值不需要很长的寿命,我不确定内存模型的内部是如何工作的,但我认为volatile 可以成为一部分,如果你想实现它,更宽松的记忆模型。我的观点是我不需要放松任何东西,我知道我在多个线程的构造中需要一些东西。
  • @user2485710:你从哪里得到volatile的解释?这绝对是不正确的。 volatile 指定存储的值可能会在没有通知的情况下更改,并且写入和读取变量都可能具有任意副作用。因此,不允许编译器对volatiles 执行优化,例如优化掉未使用的引用。它与寿命无关。
【解决方案2】:

CPU 缓存不可编程。它使用自己的内部逻辑来确定要缓存哪些内存区域。通常,它会缓存 CPU 刚刚访问过的内存,或者其预测逻辑确定 CPU 很快会访问的内存。在多处理器系统中,每个 CPU 可能有自己的缓存,或者不同的 CPU 可能共享一个缓存。如果有多个缓存,一个内存区域可能同时缓存在多个缓存中。

如果所有线程必须在查找表中看到相同的值,那么最好使用单个表。这可以通过具有static 存储持续时间的变量来实现。如果可以修改数据,那么您可能还需要std::mutex 来保护对表的访问并避免数据竞争。无需额外同步即可共享只读数据;在这种情况下,最好将其声明为 const 以明确只读性质并避免意外修改。

void foo(){
     static const int lut[]={...};
}

您使用thread_local,其中每个线程必须拥有自己的数据副本,通常是因为每个副本都将被独立修改。例如,您可以选择使用thread_local 作为随机数生成器,这样每个线程都有自己的RNG,它独立于其他线程,并且不需要同步。

void bar(){
    thread_local RandomNumberGenerator rng; // one per thread
    auto val=rng.nextRandomNumber(); // use the instance for the current thread
}

【讨论】:

    猜你喜欢
    • 2014-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-27
    • 1970-01-01
    • 2018-12-17
    • 2013-03-23
    相关资源
    最近更新 更多