4

我的代码适用于具有不同布局的大型数据块。布局会决定哪些部分数据是固定的,哪些数据是不固定的。一旦数据被固定在一个块中,它通常就不会再改变了。所以所有代码读取数据总是会看到相同的数据。

但是,其他服务可以在这些块中进行更改,只要它们确定没有代码会读取块的那部分。为了简化代码,包含更改的块将从一个服务发送到另一个服务,而不管块的布局如何。然后接收服务将覆盖整个块,包括未更改的数据。让我用一个例子来说明这一点:

假设我们有以下数据块:

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 个值没有改变,尽管它们被传输逻辑覆盖。

问题是:这是一场数据竞赛吗?如果其他线程可以同时读取数据,是否允许覆盖具有完全相同值的内存地址?

4

2 回答 2

3

这是一场数据竞赛吗?

是的。

如果其他线程可以同时读取数据,是否允许覆盖具有完全相同值的内存地址?

不是明确的——这也不是唯一的问题。

如果您的编译器实际上执行单个 8 字节加载,那么您在最后 3 个字节上有一个真实的(即,甚至可能不仅仅是理论上的)数据竞争。假设您有一台假设机器,其中uint64_t57 23 98 17 25 00 00 42是陷阱表示,使用了更新线程,并且它向后memmove复制更新。

但是,数据竞争意味着行为未由标准定义。它可能在特定平台上定义良好 - 例如任何没有整数陷阱表示的平台,任何您知道编译器将真正使用字节加载的平台,或任何具有幂等存储语义的平台。

例如,参见[intro.races] 注释 23

引入对潜在共享内存位置的推测读取的转换可能不会保留本文档中定义的 C++ 程序的语义,因为它们可能会引入数据竞争。 但是,它们通常在针对具有明确定义的数据竞争语义的特定机器的优化编译器的上下文中有效。 它们对于不容忍竞争或提供硬件竞争检测的假设机器无效

(对于非投机性比赛没有这样的说明,对于您的幂等存储也没有特别的例外,但在 IMO 采取相同的方法是合理的)。

显然,如果您可以编写代码使其依赖于这些平台细节,那么在面对编译器和/或平台更新时,它将更加便携且不那么脆弱。只是在块的两个版本之间进行原子交换(因此您从保证不变的副本读取,并写入保证不共享的副本)将始终正确,并且由于减少缓存/一致性流量甚至可能更快。

于 2021-06-14T11:26:38.413 回答
0

有这么多“取决于”,无法用明确的陈述来回答这个问题。

如果这个结构是一些生产性代码的一部分,那么答案是:肯定是的。可能会出现竞争条件。必须使用任务同步机制。没有讨论的可能。

于 2021-06-14T10:48:09.710 回答