Я не могу использовать mmap для совместного использования хэша между процессами

#mmap #crystal-lang

#mmap #crystal-lang

Вопрос:

Я внедряю многопроцессорную библиотеку, которая предоставляет структуру данных для общей памяти. Но сейчас у меня возникли проблемы, я изменил общий объект хэша в дочернем процессе, но родительский процесс все еще не прочитал измененное значение.

Пример кода:https://play.crystal-lang.org/#/r/6n34

Модифицированный с тем же указателем, почему он неэффективен?

Комментарии:

1. Обратите внимание, что, поскольку Hash это ссылочный тип, sizeof(Hash(String, String)) фактически будет размер указателя. Вы можете использовать instance_sizeof , чтобы получить фактический размер указанной памяти: play.crystal-lang.org/#/r/6nf1 Но даже тогда Hash реализация имеет указатели на какую-то другую память, и эта память нелегко доступна..

Ответ №1:

Когда вы разветвляете процесс, его память копируется с сохранением тех же адресов виртуальной памяти.

Вы просто помещаете указатель в раздел общей памяти, поэтому ваш макет памяти перед разветвлением:

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

После разветвления указатель ссылается на частную память каждого процесса соответственно:

   --------------------      -------------------- 
 |    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 типы, такие как String или Hash , в структуру, так как, опять же, это всего лишь указатели на частное адресное пространство каждого процесса. Crystal предоставляет типы и API, чтобы упростить совместное использование, например, строки, а именно Slice и String#to_slice и т.д., Но вам нужно копировать ее в общую память и из нее каждый раз, когда вы хотите передать ее или преобразовать обратно соответственно, и вы должны заранее знать свою (максимальную) длину строки.