multithreading - 在刷新和取消映射之前,是否需要同步从不同线程对内存映射文件的写入?

标签 multithreading synchronization atomic mmap memory-mapped-files

假设我有内存映射文件并从不同的线程写入它(写入永远不会重叠并且彼此独立)。我想将已经写入的数据与磁盘同步并执行 msyncFlushViewOfFile ,然后取消映射文件。

我是否需要将写入线程与刷新线程同步,例如在写入器上使用释放内存栅栏并在刷新器上使用获取栅栏?我担心此时某些写入仍会在 CPU 缓存中,而不是在主内存中。

CPU 和操作系统是否保证仍在 CPU 缓存上的写入最终会进入磁盘,或者我应该首先确保写入到达主 RAM,然后才刷新并取消映射文件?

我的线程不会从映射页面读取数据,如果可能的话,我只想使用宽松的原子操作来跟踪我的数据。

我尝试做的伪代码:

static NUM_WRITERS: AtomicUint = 0;
static CURRENT_OFFSET: AtomicUint = 0;

fn some_thread_fn(void* buffer, payload: &[byte]){
   NUM_WRITERS.fetch_add(1, memory_order_relaxed);
   offset = CURRENT_OFFSET.fetch_add(payload_size, memory_order_relaxed);
   void* dst = buffer + offset;
   memcpy(dst, payload, payload_size);
   
   // Do I need memory fence or fetch_sub(Release) here?
   compiler_fence(memory_order_release); // Prevent compiler from reordering instructions
   NUM_WRITERS.fetch_sub(1, memory_order_relaxed);

   if offset + payload_size < buffer_size {
      // Some other thread is responsible for unmapping.
      return;
   }

   while (NUM_WRITERS.load(relaxed) > 0) {
      mm_pause();
   }

   // Do I need acquire memory fence here?
   compiler_fence(memory_order_acquire); // Prevent compiler from reordering instructions
   flush_async(buffer);
   unmap(buffer);
}

最佳答案

所有商店都需要发生在munmap之前,否则可能会出错。它们将被写入磁盘,除非系统在此之前崩溃。对于受 msync 影响的数据,请确保写入(分配/memcpy)发生在该地址范围上的 msync 之前。

fdatasyncmunmap 之后的 fd 上是一种更简单的方法,可以确保所有脏数据尽快写入磁盘,除非您还有文件的其他区域不想想要同步。如果没有任何手动同步,页面缓存中的脏页会在超时(例如 15 秒)后排队等待写回磁盘。

Sequenced-before 是happens-before 的一种足够强大的形式,,因为调用 munmapmysnc 会“观察”从这个线程。例如,在单个线程中,页表条目中的“脏”标志位被任何后续内核代码看到(例如在msync系统调用期间)对于由该线程的存储指令修改的页面。 (或者通过您已同步的任何其他线程。)

我认为在你的情况下,是的,你确实需要每个线程NUM_WRITERS.fetch_sub(1, memory_order_release);,并且while (NUM_WRITERS.load(acquire) > 0) {pause } 对于到达该自旋等待循环以进行清理的一个线程。 mm_pause() 是 x86 特定的;在 x86 上,acquire 加载是免费的,与 relaxed 相同。所有 RMW 都需要lock,例如lock add,与 seq_cst 足够强大的 asm 相同。如果您计划移植到其他 ISA,那么请放心,AArch64 具有相对高效的获取释放

relaxed 可能在大多数情况下都有效,但理论上会允许该线程在其他线程到达存储指令之前进行 munmap 并使页表条目无效,导致故障。或者更合理的是让一些存储发生在 mysnc 之后。

使用缓存一致性 DMA(例如在 x86 上),只有设备对内存的实际 DMA 读取才是存储提交到缓存的最后期限。 (但是对于 msync 首先注意到页面是脏的并将其排队写入磁盘,在操作系统检查硬件页表之前至少必须写入其中的一个字节。 )

当存储指令在 TLB 条目显示 D(脏)位 = 0 的页面上运行时,在 x86 上,该内核采用微代码辅助以原子方式 RMW 页表条目D=1。 ( https://wiki.osdev.org/Paging#Page_Directory ) 还有一个 A 位,即使通过读取也会被设置。 (操作系统可以清除它并很快查看哪些页面再次设置了它;这些页面对于驱逐来说是不好的选择。)


您不需要手动atomic_signal_fence(又名compiler_barrier),因为编译器已经无法将存储通过函数调用移动到可能读取存储数据的函数。 (如arr[i] = 1; foo(arr),其中foomsyncmunmap,确切地说同样的原因,CPU 不知道的用户定义函数是安全的。)执行无序执行的 CPU 将保留按程序顺序运行的单个线程的错觉。


如果每次写入都具有页面粒度,则可以让每个线程在其写入的页面上执行自己的 msync 操作。如果写入小于页面,这将不是很好,因为您会为同一页面触发多个磁盘 I/O。

关于multithreading - 在刷新和取消映射之前,是否需要同步从不同线程对内存映射文件的写入?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77663688/

相关文章:

java - 多线程 - 杀死一个线程及其所有子进程

c# - 任务同步的正确方法?

linux - 用于更新计算机之间文件差异的脚本

concurrency - 使用单个值更新原子

c++ - 通过 shared_ptr 返回的 Singleton 对象是线程安全的吗?

database - 处理数据库中的 PayPal API 调用和记录的良好做法是什么?

c# 在线程期间从外部方法更新进度表

java - 线程与单例对象或字符串的同步连接

c++ - 使用我的线程实现时程序失败?

java - 类的 Getter 方法是线程安全的吗?