假设我有一个具有多个线程的应用程序,需要访问一些共享数据。
我知道可以使用互斥体(关键部分)来确保“最多”一次有一个线程可以访问共享数据。这可以防止一个线程读取共享数据而另一个线程当前正在修改共享数据的情况,否则可能导致读取不一致的状态。互斥锁还可以防止多个线程同时修改共享数据的情况,否则可能会导致写入冲突。 现在,在具有多个处理器或多CPU核心的机器上,还有另一个问题:如果在处理器#1上运行的线程
A
修改了共享数据,然后在处理器#2上运行的线程
B
读取了共享数据,线程B
仍然可能“看到”一些过时的数据——即使强制执行了互斥。这是因为,一般来说,每个处理器(核心)都有自己的本地 CPU 缓存。因此,除非刷新缓存,否则即使一个 CPU 的主内存 (RAM) 中的共享数据被修改,其他 CPU 的本地缓存中仍然可以有“旧”版本! 据我了解,
单独并不能解决这个缓存问题,因为互斥体就是强制执行特定的访问顺序,但是,一般来说,互斥体对CPU缓存没有任何作用 。毕竟,甚至没有一种方法可以将互斥锁“绑定”到某些内存地址,当获取或释放互斥锁时,这些内存地址需要从 CPU 缓存中刷新。 那么,我们在实际中如何处理这个“缓存同步”问题呢? “记忆栅栏”到底是如何融入图片中的?我知道它们的存在是为了防止内存访问重新排序,但是,同样,“不清楚”这如何影响多个 CPU。我是否需要将 互斥体 与 内存栅栏
结合起来以防止线程从本地缓存读取过时
数据?
我已阅读互斥体文档(例如Enter/LeaveCriticalSection()
),但它确实没有阐明互斥体函数将如何与处理器缓存交互......
另外,我读到可以分配内存页面,例如通过 VirtualAlloc()
函数,使用特殊的
在具有多个内核或处理器的环境中。在并发编程时,确保同步并防止CPU缓存引起的数据不一致问题非常重要。现在让我们深入研究您的问题并一一解答;
缓存同步和互斥:
事实上,互斥体本身并不能自动处理缓存同步。当线程获取互斥体并修改共享数据时,无法保证对共享数据所做的更改立即对不同处理器上运行的其他线程可见。 内存栅栏:在多线程环境中,您可以使用内存栅栏来确保某些内存操作(读或写)在其他内存操作之前或之后完成。例如,在 C++ 中,您可以使用 std::atomic_thread_fence 等函数或特定于平台的指令来创建内存栅栏。
互斥体和内存栅栏组合:
将互斥锁与适当的内存栅栏相结合是解决缓存同步问题的常见做法。当您获取互斥体时,通常意味着获取锁并且应该附带必要的内存同步。现代互斥体实现通常包含内存屏障,以确保内存操作在不同的 CPU 核心之间可见。
具有 PAGE_NOCACHE 的内存页面:
使用 PAGE_NOCACHE 标志分配内存页确实可以用来避免缓存问题。然而,这种方法更加激进,可能会导致性能下降,因为它迫使系统每次绕过 CPU 缓存直接从 RAM 获取数据。当需要对缓存行为进行细粒度控制时,通常会使用它,但并非在所有情况下都需要它。
实用方法:
在实践中,依靠操作系统或线程库提供的精心设计的互斥体实现通常可以处理必要的缓存同步。将互斥锁与内存栅栏相结合是一种可靠的方法,可以确保跨多个线程和处理器的内存操作正确同步和排序。