我有可处理具有不同布局的大数据块的代码。布局将决定哪些数据是固定的,哪些数据是不固定的。一旦数据被固定在一个块中,它通常就不会再改变。所以所有读取数据的代码总是会看到相同的数据。
但是,其他服务可以在这些块中进行更改,只要它们确定没有代码将读取该块的该部分。为了简化代码,包含更改的块将从一个服务发送到另一个服务,无论块的布局如何。然后,接收服务将覆盖整个块,包括未更改的数据。让我用一个例子来说明这一点:
假设我们有以下数据块:
57 |
23 |
98 |
17 |
25 |
00 |
00 |
00 |
00 |
00 |
想象一下前 5 个值是“固定的”。我们服务中的代码只会读取前 5 个值,而不会读取接下来的 5 个值。由于我们的架构设计,我们可以保证这一点。接下来的 5 个值实际上没有意义,因此我在表中添加了零来说明这一点。
现在另一个服务确定接下来的 5 个值,将完整的块发送到我们的服务,我们只需用新数据覆盖完整的块即可。由于前 5 个值是“固定的”,因此它们保持不变,但是传输和覆盖块的代码不知道块的布局,因此它唯一能做的就是覆盖整个块。这是结果:
57 |
23 |
98 |
17 |
25 |
08 |
33 |
42 |
71 |
85 |
如前所述,前 5 个值没有改变,尽管它们被传输逻辑覆盖。
问题是:这是一场数据竞赛吗?如果其他线程可以同时读取数据,是否允许用完全相同的值覆盖内存地址?
这是一场数据竞赛吗?
Yep.
如果其他线程可以同时读取数据,是否允许用完全相同的值覆盖内存地址?
没有明确 - 这也不是唯一的问题。
如果您的编译器实际上执行单个 8 字节加载,则您有一个real(即,甚至可能不仅仅是理论上的)最后 3 个字节上的数据竞争。假设您有一台假设的机器,其中uint64_t
value 57 23 98 17 25 00 00 42
是一个陷阱表示,您的更新线程使用memmove
,并复制更新向后.
然而,数据竞争意味着行为是不确定的按标准。它可能在特定平台上定义良好 - 例如任何没有整数陷阱表示的平台,您知道编译器将真正使用字节加载的任何平台,或者具有幂等存储的显式语义的任何平台。
例如,参见[介绍种族] 注 23 https://timsong-cpp.github.io/cppwp/intro.races#note-23:
引入对潜在共享内存位置的推测性读取的转换可能不会保留本文档中定义的 C++ 程序的语义,因为它们可能会引入数据争用。然而,它们通常在优化编译器的上下文中有效,该优化编译器针对具有明确定义的数据竞争语义的特定机器。对于不容忍竞争或不提供硬件竞争检测的假设机器,它们将无效
(对于非投机竞赛没有这样的注释,对于幂等存储也没有特定的例外,但在 IMO 中采取相同的方法是合理的)。
显然,如果您可以编写代码,那么它doesn't依赖于这些平台细节,在面对编译器和/或平台更新时,它将更加可移植且不易脆弱。仅在块的两个版本之间进行原子交换(因此您从保证不变的副本中读取,并写入保证不共享的副本)将始终是正确的,并且由于减少了缓存/一致性流量,甚至可能会更快。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)