我不能使用 mmap 在进程之间共享哈希

I can’t use mmap to share a Hash between processes

我正在实现一个为共享内存提供数据结构的多进程库。 但是我现在遇到了麻烦,我修改了子进程中的共享Hash对象,但是父进程还是没有读取到修改后的值

示例代码:https://play.crystal-lang.org/#/r/6n34

用同一个指针修改,为什么没有生效?

当你 fork 一个进程时,它的内存被复制,同时保留相同的虚拟内存地址。

你只是将一个指针放入你的共享内存部分,所以你在 fork 之前的内存布局是:

 +--------------------+    +--------------------+
 |    Shared memory   |    |     Parent heap    |
 |                    |    |                    |
 |                    |    |                    |
 |  Virtual address   |    |  +---------+       |
 |        of  --------------> | Hash    |       |
 |                    |    |  +---------+       |
 |                    |    |                    |
 +--------------------+    +--------------------+

fork后指针分别指向每个进程的私有内存:

 +--------------------+    +--------------------+
 |    Shared memory   |    |     Parent heap    |
 |                    |    |                    |
 |                    |    |                    |
 |  Virtual address   |    |  +---------+       |
 |        of  --------------> | Hash    |       |
 |                 |  |    |  +---------+       |
 |                 |  |    |                    |
 +--------------------+    +--------------------+
                   |
                   |
                   |       +--------------------+
                   |       |     Child heap    |
                   |       |                    |
                   |       |                    |
                   |       |  +---------+       |
                   +--------> | Hash    |       |
                           |  +---------+       |
                           |                    |
                           +--------------------+

因此,当您取消引用子对象中的指针时,您只会接触到子对象堆中的对象。

您要做的是将所有实际数据放入共享内存中。这对于标准 Crystal 数据类型来说很难做到,因为它们依赖于能够请求新内存并由垃圾收集器管理它。所以你需要实现一个可以在共享内存上工作的 GC。

然而,如果您只有固定数量的数据,比如几个数字或固定大小的字符串,您可以利用 Crystal 的值类型使事情变得更好一点:

module SharedMemory
  def self.create(type : T.class, size : Int32) forall T
    protection = LibC::PROT_READ | LibC::PROT_WRITE
    visibility = LibC::MAP_ANONYMOUS | LibC::MAP_SHARED
    ptr = LibC.mmap(nil, size * sizeof(T), protection, visibility, 0, 0).as(T*)
    Slice(T).new(ptr, size)
  end
end

record Data, point : Int32 do
  setter point
end

shared_data = SharedMemory.create(Data, 1)
shared_data[0] = Data.new 23

child = Process.fork
if child
  puts "Parent read: '#{shared_data[0].point}'"
  child.wait
  puts "Parent read: '#{shared_data[0].point}'"
else
  puts "Child read: '#{shared_data[0].point}'"
  # Slice#[] returns the object rather than a pointer to it, 
  # so given Data is a value type, it's copied to the local 
  # stack and the update wouldn't be visible in the shared memory section.
  # Therefore we need to get the pointer using Slice#to_unsafe 
  # and dereference it manually with Pointer#value
  shared_data.to_unsafe.value.point = 42
  puts "Child changed to: '#{shared_data[0].point}'"
end
Parent read: '23'
Child read: '23'
Child changed to: '42'
Parent read: '42'

https://play.crystal-lang.org/#/r/6nfn

这里需要注意的是,您不能将任何 Reference 类型(例如 StringHash 放入结构中,因为这些只是指向每个进程的私有地址的指针 space。 Crystal 提供类型和 API 来使共享例如字符串更容易一些,即 SliceString#to_slice 等,但是你必须将它复制到共享和从共享每次要传递或转换回内存时,您必须提前知道您的(最大)字符串长度。