缓存读取主要值:时间查找比原子操作便宜吗?

时间:2015-08-03 08:55:17

标签: multithreading concurrency operating-system cpu

我的多线程应用程序使用了大量读取值。这些值是配置值,仅在操作员编辑配置文件时指示应用程序重新加载配置文件而不停机时才会更改。可以从多个线程访问这些值。没有任何线程改变该值。只有在重新加载配置文件时才会发生变异。

由于值可以更改,因此访问它们需要某种形式的同步。但由于它们变化很少,我不想使用互斥锁:

  • 正常的互斥锁不允许多个线程同时访问这些值。由于线程只读取值,因此只要配置文件没有被重新加载,线程的并发访问就是安全的。
  • 读写互斥锁听起来像是一个很好的解决方案,但它们的开销很高。

我可以降低级别并直接使用原子操作。例如,我可以使用最新版本的原子指针使Config对象不可变:

  • 重新加载配置文件后,我创建了一个新的Config对象,并以原子方式更新指向当前活动的Config对象的指针。
  • 然后读者线程只是原子地加载指针并使用Config对象而不进行同步,因为它是不可变的。

然而,原子操作本身也不是免费的。我很难找到他们确实施加什么样的开销的信息(CPU管道停顿?CPU内核之间的某种通信开销?他们是否限制了并发性?不确定。)但我觉得它更好尽可能避免使用它们。

所以我想到了在有限的时间内缓存配置指针,例如持续1秒无需同步即可访问缓存的指针。但是这假设时间查找比原子指针操作更便宜并且对并发性的影响更小。这是真的吗?

所以我的主要问题是:

  • 时间查找比原子指针操作便宜吗?
    • 开销是否取决于粒度?例如,秒粒度时间查找是否比纳秒粒度更便宜?
    • 我最感兴趣的是Linux,但也欢迎有关其他平台的信息。

我的次要问题(为了更好地理解问题)是:

  • 在各种操作系统上查找时间的确切开销是多少?在查找时间期间会发生什么?是否需要调用内核(系统调用)?
  • 原子指针加载和存储的开销到底是什么? CPU究竟发生了什么?

有关我的环境和用例的其他信息:

  • 我正在使用Go这个应用程序,但我对一般的,语言无关的信息感兴趣。我还有另一个大量并发的C ++应用程序,所以我希望尽可能与语言无关的答案。
  • 我的主要制作平台是x86_64 Linux,所以我最感兴趣的是有关该平台的信息。但由于我的应用程序被各种各样的用户使用,我必须至少了解其他平台的注意事项,即使我没有特别优化它们。

1 个答案:

答案 0 :(得分:1)

我会给出一个部分答案:

所有低级语言都可以通过廉价和原子方式从内存中加载一个值。这不需要在x86上进行互锁访问。实际上,在x86上它只是一个常规的加载指令。我们需要做的就是阻止编译器和运行时(如果有的话)重新排序这个内存访问。这是一个编译障碍。

在C#中,这个工具通过Volatile类浮出水面。在C ++中,现在有atomic。我不确定需要什么样的一致性级别。它可能是一个获取负载(在x86上很便宜)。 MSVC还将这些语义应用于volatile个变量。我不知道其他编译器。 C标准肯定会为volatile指定这些语义,但有些编译器会这样做。

这个设施很便宜,我认为你可以停止寻找其他任何东西。此操作几乎总是命中共享的CPU缓存行(除非在创建存储之后)。

有关详细信息,请参阅Herb Sutters视频系列“Atomic Weapons”。坦率地说,他也是比我更可靠的来源。