使用phtreads从C中的多个线程在不同位置读取和写入数组是否安全?

ins*_*aze 5 c multithreading pthreads

假设有两个线程A和B。还有一个共享数组:float X[100]

线程A一次向数组中一次写入一个元素,每10步更新一次index(以安全的方式)指示当前索引的共享变量,并且还将信号发送给线程B。信号,它会index以安全的方式读取,然后继续读取X直到position 的元素index

这样安全吗?线程A确实更新了数组还是仅更新了缓存中的副本?

Ala*_*got 2

这样做安全吗?

如果您的数据修改是安全的,并受到关键部分、锁或其他内容的保护,那么这种访问对于涉及硬件访问的情况来说是完全安全的。

线程 A 真的更新了数组还是只是缓存中的副本?

只是缓存中的一个副本。大多数高速缓存目前都是回写式的,并且当一行被从高速缓存中弹出(如果已被修改)时,仅将数据写回内存。这极大地提高了内存带宽,尤其是在多核环境中。

但一切都发生了,就好像内存已被更新一样。

对于共享内存处理器,通常有缓存一致性协议(除了一些用于实时应用程序的处理器)。这些协议的基本思想是状态每个缓存行相关联。
状态描述了有关不同处理器的高速缓存中的行的信息。
例如,这些状态指示该行是否仅存在于当前缓存中,或者由多个缓存共享,与内存同步,无效......请参见例如流行的 MESI 缓存一致性协议的描述。

那么,当缓存行被写入并且也存在于另一个处理器中时,会发生什么?
由于该状态,高速缓存知道一个或多个其他处理器也具有该行的副本,并且它将发送无效信号。该行将在其他缓存中失效,当它们想要读取或写入它时,必须重新加载其内容。实际上,此重新加载将由具有有效副本的缓存来服务,以限制内存访问。

这样,虽然数据仅写入缓存中,但其行为类似于数据已写入内存的情况。

但是,尽管硬件在功能上将确保传输的正确性,但必须考虑缓存的存在,以避免性能下降。
假设高速缓存 A 正在更新一行,而高速缓存 B 正在读取该行。每当缓存 A 写入时,缓存 B 中的行就会失效。每当高速缓存 B 想要读取该行时,如果该行已失效,则它必须从高速缓存 A 中获取该行。这可能会导致该行在高速缓存之间进行多次传输,并导致内存系统效率低下。

因此,就您的示例而言,10 可能不是一个好主意,您应该使用缓存上的信息来改善发送方和接收方之间的交换。

例如,如果您使用的是具有 64 字节缓存行的 pentium,则应将 X 声明为

_Alignas(64) float X[100];
Run Code Online (Sandbox Code Playgroud)

这样, 的起始地址X将是 64 的倍数并且适合高速缓存行边界。该_Alignas限定符自 C17 起就存在,通过包含 stdalign.h,您也可以类似地使用alignas(64). 在 C17 之前,大多数编译器都有多种扩展,以便实现对齐放置。
当然,您应该指示进程 B 仅在写入完整的 64 字节行(16 个浮点数)后才读取数据。

这样,当线程 B 访问数据时,线程 A 不会再修改缓存行,并且缓存 A 和 B 之间只会发生一次初始传输。缓存之间传输数量的减少可能会对性能产生重大影响,具体取决于您的程序。