【问题标题】:OS-wide synchronized access to MappedByteBuffer操作系统范围内对 MappedByteBuffer 的同步访问
【发布时间】:2013-12-22 06:11:50
【问题描述】:

我可以让 2 个 Java 进程通过 MappedByteBuffer 共享一个内存区域。

但是如果这两个进程可以同时读取或写入偏移量x 处的字节(从共享内存的开始),我如何同步访问这个字节?

这是期望的用法:

假设:每个进程只有 1 个线程会尝试读写访问。

                           |
========================== | ============================
     Process A             |         Process B
========================== | ============================
                           |
lock(sharedMemory[x]);     |    lock(sharedMemory[x]);      (Line 1.)
  ...                      |      ...
// read from byte at x     |    // write to byte at x
a = sharedMemory[x];       |    sharedMemory[x] = b;
  ...                      |      ...
unlock(sharedMemory[x]);   |    unlock(sharedMemory[x]);    (Line 20.)
========================== | ============================
                           |

上图中时间向下流动。

因此,只有进程 A 或进程 B(但不能同时执行)可以在给定时间执行第 1 到 20 行。

【问题讨论】:

  • 您似乎正在尝试有效地重新实现 IPC。多年来,操作系统已经为 IPC 提供了多种有用的机制,其中一些并没有遇到这个问题。你确定你需要在这里重新发明轮子吗?
  • 我的问题中有一个非常重要的关键字:Java
  • 是的,Java 可以调用原生库。 Java 还支持众所周知的 IPC 机制 TCP/IP 套接字。
  • 你可以使用 FileLock:写独占锁,读共享锁。
  • @SamiKorhonen 嗯,现在我为什么没有想到呢?! :-) 请考虑发布您的评论作为答案。我将相对于彼得的评估它,并选择更简单的替代方案(几天后)。谢谢!

标签: java shared-memory synchronize


【解决方案1】:

在 Java 中没有标准的方法来做到这一点。你可以尝试做的是使用 Unsafe,创建你自己的锁,就像我在这个库中所做的那样 AbstractByte in OpenHFT/Java-Lang 请参阅 tryLock、busyLock、解锁方法。

我还没有测试这在跨进程中的效果如何。目前我只有一个进程在写。

如果您有一位作者和一位读者,则无需锁定记录。您可以使用内存屏障或时间戳保护。

一个简单的时间戳保护可以像这样工作。

作者:

- on start, 
    - if even, increment to odd with memory barriers.
    - if odd, retry until even.
- perform multiple writes
- on finish, increment the counter to an even number

读者:

- on start, (with a read barrier)
     - if even, take a copy of the counter.
     - if odd, retry until even.
- read the multiple values.
- read the counter again, if the counter changed, loop until it doesn't.

当然,如果你有一个单一的值,你只需要一个有序的写入和一个易失的读取,所以根本不需要锁定。


编辑:我添加了一个示例LockingViaMMapMainLockingViaFileLockMain 这使用两个进程来锁定共享内存映射文件中的记录。一个进程锁定并将标志设置为假(仅当为真时),第二个进程将标志设置为真(仅当为假时)

这两个进程打印在我的笔记本电脑上

Toggled 10,000,128 times with an average delay of 31 ns

为了比较,使用 FileLock 的基准看起来像

Toggled 10,000,128 times with an average delay of 2,402 ns

FileLock 非常快,只需 2.4 微秒,但在我的机器上使用文件中的单个锁快 80 倍。

如果 FileLock 足够快,你应该使用它,因为

  • 它不会改变文件。
  • 如果进程终止,锁会自动释放。
  • 它应该适用于网络安装的文件系统。我不希望内部锁在系统之间起作用。

有趣的代码读取

File tmpFile = new File(System.getProperty("java.io.tmpdir"), "lock-test.dat");
FileChannel fc = new RandomAccessFile(tmpFile, "rw").getChannel();
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, RECORDS * RECORD_SIZE);
ByteBufferBytes bytes = new ByteBufferBytes(mbb.order(ByteOrder.nativeOrder()));


// the lock and toggle.
bytes.busyLockLong(recordOffset + LOCK);
try {
    boolean flag = bytes.readBoolean(recordOffset + FLAG);
    if (flag == toggleTo) {
        if (t % 100 == 0)
            System.out.println("j: " + j + " is " + flag);
        continue;
    }
    bytes.writeBoolean(recordOffset + FLAG, toggleTo);
    break;
} finally {
    bytes.unlockLong(recordOffset + LOCK);
}

【讨论】:

  • @Harry 在构建自己的锁定结构时需要内存屏障。如果你使用 synchronized 和 Lock 这些为你做内存屏障,但进程之间没有任何东西可以工作,所以你必须自己做。
  • 算法似乎坏了。有可能,读取器在写入器执行最终计数器增量之前完成。
  • @SamiKorhonen 谢谢,我遗漏了一些细节。这也应该适用于多个作家。
  • @Harry 如果您想锁定整个文件并且您不太关心性能,这很好。 FileLock 有一些优点,例如;它不会改变文件,如果你在持有锁时进程死了,它会自动释放,它应该可以在网络文件系统上工作,并且代码应该更干净。
  • @Harry 我添加了一些 FileLock 的性能数据以及使用 FileLock 的原因。
猜你喜欢
  • 1970-01-01
  • 2011-04-14
  • 2012-03-02
  • 1970-01-01
  • 2016-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-14
相关资源
最近更新 更多