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