【问题标题】:Cross-platform and cross-process atomic int writes on file跨平台和跨进程原子 int 写入文件
【发布时间】:2011-02-20 05:32:57
【问题描述】:

我正在编写一个应用程序,它必须能够处理对它的许多并发访问,无论是线程还是进程。因此,不应对此应用互斥锁或锁。

为了使锁的使用降到最低,我将文件设计为“仅附加”,因此所有数据首先附加到磁盘,然后指向它已更新的信息的地址, 改为指新的。所以我只需要实现一个小锁系统来更改这个 int 以便它引用新地址。 最好的方法是什么?

我在考虑可能在地址前放置一个标志,当它被设置时,读者将使用自旋锁直到它被释放。但我担心它根本不是原子的,是吗? 例如

  • 读取器读取标志,但未设置
  • 同时,写入器写入标志并更改 int 的值
  • 阅读器可能读取到不一致的值!

我正在寻找锁定技术,但我发现的只是线程锁定技术,或者锁定整个文件,而不是字段。难道不能这样做吗?仅附加数据库如何处理这个问题?

编辑: 我正在研究仅附加数据库(couchDB)是如何做到的,似乎他们只使用一个线程来序列化对文件的写入。这是否意味着如果不使用文件系统锁锁定整个文件,就无法像 sqlite 一样使它们可嵌入?

谢谢! 考埃

【问题讨论】:

  • 是否可以通过制作两个锁并以与写入相反的顺序读取它们来实现丑陋的解决方案?

标签: file-io locking mutex blocking spinlock


【解决方案1】:

当我需要做这样的事情时,通常我会编写一个进程来接受来自其他进程的多个连接来获取数据。此日志记录过程可以维护一个文件指针,它正在将所有数据写入其中,而不会冒多次写入到同一个地方的风险。

记录进程中的每个线程只会监听新的输入并将其提交到队列中,而不会阻塞生成数据的进程。尝试在生成要记录的数据的线程中执行此操作(写入磁盘)最终会使您处于必须进行锁定操作并遭受它们所需的任何性能影响的位置。

【讨论】:

  • 感谢您的回答!但是,如果读取器碰巧正在读取写入器进程正在写入的数据——即使它只是一个 int 指针,它仍然可以在不一致的状态下捕获它,不是吗?另外,我想避免使用这种进程通信,因为我想让这个数据库原型可以嵌入,就像 sqlite 一样。
【解决方案2】:

注意文件系统的附加语义 - 它可能不提供原子附加操作。

一种选择是将您的文件作为共享的内存映射 (mmap),然后在指针上执行原子内存操作,例如比较和交换。你的成功将取决于你的操作系统是否有这样的操作(Linux、OSX 都有)。

使用rename 实现您想要的正确(尽管我不确定它是否很快)的方法是 - 它是大多数文件系统上的原子文件操作。将最新数据保存在官方文件位置。要更新数据,请将新数据写入临时文件,然后将该临时文件重命名为官方位置。

【讨论】:

  • 感谢您的回答!我不知道 mmap 也可以写入文件,我认为它只会从中读取。如果 Windows 等效项 (MapViewOfFile) 也可以按预期工作,那么它可能是一种选择。但我不知道是否有跨处理器的方式来使用比较和交换功能。关于附加语义,我认为普通的文件系统文件锁在这种情况下可以正常工作,不是吗?重命名是没有问题的。这是我正在研究的数据库原型,它应该能够处理非常大的文件和不断的写入。
  • 将页面映射到数组然后在需要写入时对它们使用比较和交换是否是一个很好的工作流程。 mmap 页面会很慢吗? mmap 保证是原子的吗?我没有找到任何说它是的参考,但也没有一个说它不是!
  • 对我来说听起来像是一个很好的工作流程。 mmap 的页面并不慢(至少在 linux 上),实际上它们应该比使用读/写更快,因为它们避免了任何复制(虚拟内存直接映射到文件系统缓存中的页面)。它必须在 Windows 上完全一样才能工作 - 两个进程中的两个 mmap 区域需要映射到相同的物理内存,以便比较和交换工作。 mmap 调用是否是原子的与您的情况无关。您需要的唯一原子性是对已映射内存的比较和交换操作。
  • 但是我在 mmap 上使用比较和交换是不是会发生这种情况,但是将它从内存实际更新到实际硬盘所需的时间可能会导致一些同步问题?
  • 它应该也可以跨进程工作,只要你将它共享,相同的物理内存页面将支持所有已映射的区域。
猜你喜欢
  • 2011-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-28
  • 2021-08-14
  • 2016-09-08
  • 1970-01-01
相关资源
最近更新 更多