LINUX.ORG.RU

Разделяемая память для процессов на Python и не только.

 , , , ,


2

4

Привет всем!

Есть большое многопроцессное приложение, с рабочими процессами, всякими GUI и логгерами в отдельных процессах и т.д. Построено с использованием модуля multiprocessing. Рабочие процессы обрабатывают большие данные. Для больших данных используется multiprocessing.Array, вот так:

class Worker(mp.Process):
    def __init__(self, buffers, pipe, other):
        super().__init__()
        self._buffers = buffers
        self._pipe = pipe
    def run(self):
        param = get_message(self._pipe)
        big_calculations(self._buffers[param.a], param.b, ...)

buffers = []
for i in range(10):
    buffers.append(mp.Array(ctypes.c_uint8, buffer_size, lock=False))
...
for i in range(10):
    p1, p2 = mp.Pipe()
    worker = Worker(buffers, p1, other_param)
    worker.start()
Синхронизация от mp.Array не требуется, процессы синхронизируются с помощью посылки/отправки сообщений через p1/p2, поэтому lock=False.

Вопросы:
* Размер/количество буфера(ов) задаются до запуска рабочих процессов. Как правильно реализовать изменение количества/размера после того, как рабочие процессы уже стартовали? Т.е. понятно, как отправить сообщение. Непонятно как закрыть существующий буфер и открыть новый.
* Что у mp.Array под капотом? Я заметил, что python открывает много файлов с именами вида /tmp/pymp-ixc54qx7/pym-27111-h7wi_sy3. Это как-то связано с mp.Array?
* Очень желательно, чтобы к этой общей памяти можно было обращаться не только из процессов на Python. Возможно как-либо её открыть из другого стороннего процесса, написанного на чём-то ещё? Может быть мне тогда что-то другое использовать, а не mp.Array?

Аналогичные вопросы про mp.Pipe():
* Как в работающий процесс передать конец новой трубы?
* Как передать в не Python'овский процесс конец трубы?

Спасибо!

★★★★★

Последнее исправление: ls-h (всего исправлений: 1)
Ответ на: комментарий от Uncle_Bobby

Ищи биндинги на питоне к mmap

Есть модуль, так и называется mmap. Единственно что непонятно, это как сделать mmap без записи файла и взаимодействия с ФС. mmap (из модуля mmap) первым параметром принимает файловый дескриптор. Дескриптор может быть -1, тогда файл не создаётся, но и имени у него не будет. Как тогда поделиться этим участком памяти с другими процессами? В POSIX же есть shm_open, которая возвращает дескриптор по имени (как обычный open), но, насколько я понимаю, запись на диск при этом не производится.

ls-h ★★★★★
() автор топика
Ответ на: комментарий от ls-h

Дескриптор может быть -1, тогда файл не создаётся, но и имени у него не будет. Как тогда поделиться этим участком памяти с другими процессами?

Только через fork.

В POSIX же есть shm_open, которая возвращает дескриптор по имени (как обычный open), но, насколько я понимаю, запись на диск при этом не производится.

Да, но… В зависимости от реализации, но обычно создается файл где-нибудь в /tmp или /dev/shmю

На Linux можно смело использовать /dev/shm, т.е. создать там файл и юзать дескриптор для mmap.

Deleted
()
Ответ на: комментарий от Deleted

Только через fork.

Это не подходит. Что делать, когда нужно создать новый буфер после запуска процессов рабочих?

На Linux можно смело использовать /dev/shm

Тоже такая мысль пришла, но что-то ничего не нагуглил, как корректно это делать. Можно выбрать любое имя? Нужно вручную очищать перед завершением последнего процесса или как только не будет ни одного процесса, память будет освобождена?

ls-h ★★★★★
() автор топика
Ответ на: комментарий от ls-h

я по питону не специалист, но в доке на модуль mmap есть параметр tagname.

https://docs.python.org/3/library/mmap.html

tagname, if specified and not None, is a string giving a tag name for the mapping. Windows allows you to have many different mappings against the same file. If you specify the name of an existing tag, that tag is opened, otherwise a new tag of this name is created. If this parameter is omitted or None, the mapping is created without a name. Avoiding the use of the tag parameter will assist in keeping your code portable between Unix and Windows.

Как то мутно это написано.

Uncle_Bobby
()
Ответ на: комментарий от ls-h

Тоже такая мысль пришла, но что-то ничего не нагуглил, как корректно это делать. Можно выбрать любое имя?

mkstemp в /tmp

Нужно вручную очищать перед завершением последнего процесса или как только не будет ни одного процесса, память будет освобождена?

Нужно unlink, можно сразу после того, как все процессы открыли файл.

anonymous
()
Ответ на: комментарий от Uncle_Bobby

Как то мутно это написано.

Ага, я тоже не понял. Это какое-то вендоспецифическое. И в (Unix version) ничего такого не предлагается.

ls-h ★★★★★
() автор топика
Ответ на: комментарий от ls-h

Это не подходит. Что делать, когда нужно создать новый буфер после запуска процессов рабочих?

Если без fork() от основного процесса, то только через создание файлов. В конечном счете shm_open() делает ровно тоже самое.

Можно выбрать любое имя?

Ну имя должно быть либо уникальное и заведомо известно, либо http://man7.org/linux/man-pages/man3/mkstemp.3.html. Но при уникальном авто-генерируемом имени его нужно как-то сообщить каждому процессу в «оркестре».

Нужно вручную очищать перед завершением последнего процесса или как только не будет ни одного процесса, память будет освобождена?

В /dev/shm на актуальных linux-ах смонтирована tmpfs, и всё что там лежит будет «жить» в ОЗУ пока к inode есть ссылки:

  1. когда файл есть в какой-то директории.
  2. пока есть хотя-бы один процесс, в котором этот файл открыт или отображен в память.

Т.е. файл нужно удалить когда он станет ненужным, иначе будет просто занимать ОЗУ до перезагрузки. При этом можно удалить как только все процессы из «оркестра» открыли файл и/или отобразили в память.

Однако, как только файл будет удален «просто так» к его содержимому уже не подключишься (только открывая соответствующий дескриптор в /proc/$PID/fd/

Deleted
()
Ответ на: комментарий от Deleted

Дескриптор может быть -1, тогда файл не создаётся, но и имени у него не будет. Как тогда поделиться этим участком памяти с другими процессами?

Сделать дескриптор через memfd_create, делиться с другими процессами через /proc/<pid>/fd/<fd>.

anonymous
()
Ответ на: комментарий от ls-h

Ага, я тоже не понял. Это какое-то вендоспецифическое. И в (Unix version) ничего такого не предлагается.

В linux это реализуется созданием файла вида /dev/shm/LIKE_WINDOWS_NAMED_SHARED_MEMORY_$NAME.

Внутри Windows это реализовано примерно также, но имена этих файлов видны через Native API, а не всем. Важное отличие в том, что Windows самостоятельно удаляет такой файл с закрытием последнего дескриптора.

В linux же такое «авто-удаление» нужно реализовывать отслеживая закрытие дескрипторов, в том числе начиная танцевать с O_TMPFILE-бубном.

Deleted
()
Ответ на: комментарий от anonymous

Сделать дескриптор через memfd_create, делиться с другими процессами через /proc//fd/.

Кстати да, про относительно новый memfd_create() я забыл. Удобство в том, что имя явно видно в /proc/$PID/fd/

Но только это почти тоже самое, как если начать с fork().

Deleted
()
Ответ на: комментарий от Deleted

Но только это почти тоже самое, как если начать с fork().

Да ну нет. Созданный через memfd_create файл ты можешь открыть в паралельном процессе, о чем явно сказано в манах.

А профит ещё в том, что не надо париться с уникальным именем и руками освобождать память.

Недостаток — динамически генерированые пути, которые на глаз не отличаются от путей других процессов.

anonymous
()
Ответ на: комментарий от anonymous

Да ну нет. Созданный через memfd_create файл ты можешь открыть в паралельном процессе, о чем явно сказано в манах.

А профит ещё в том, что не надо париться с уникальным именем и руками освобождать память.

Ок, уговорил что лучше.

Но я имел в виду, что запуск нужно начинать с какого-то первого/главного процесса. А тогда можно сделать еще так = в этом процессе создать файлик c O_TMPFILE, сдвинуть его дескриптор посредством dup2() на какой-нибудь 142й номер, и потом открыть во всех потомках.

Deleted
()
Ответ на: комментарий от Uncle_Bobby

Вот примеры для питона

Спасибо!

ls-h ★★★★★
() автор топика
Ответ на: комментарий от Deleted

O_TMPFILE

У O_TMPFILE есть особенность, что файл создаётся в файловой системе в указаной в open директории. Если нужно, чтобы файл лежал в оперативной памяти, то надо найти и указать в open директорию, лежащаю в tmpfs. Иначе будет писать на диск.

anonymous
()
Ответ на: комментарий от Deleted

только открывая соответствующий дескриптор в /proc/$PID/fd/

Хм... Это можно сделать из стороннего процесса?

В конечном счете shm_open() делает ровно тоже самое.

Разве он что-то создаёт на уровне ФС? Мне казалось, там своё пространство имён.

ls-h ★★★★★
() автор топика
Ответ на: комментарий от ls-h

Хм... Это можно сделать из стороннего процесса?

open(«/proc/42/fd/71»)

man proc

Разве он что-то создаёт на уровне ФС? Мне казалось, там своё пространство имён.

man shm_open

The POSIX shared memory object implementation on Linux makes use  of  a
       dedicated tmpfs(5) filesystem that is normally mounted under /dev/shm.

anonymous
()
Ответ на: комментарий от ls-h

только открывая соответствующий дескриптор в /proc/$PID/fd/

Хм… Это можно сделать из стороннего процесса?

Да, если есть права для доступа. В этом суть /proc и unix-подхода «всё как файл».

В конечном счете shm_open() делает ровно тоже самое.

Разве он что-то создаёт на уровне ФС? Мне казалось, там своё пространство имён.

Нет особого смысла всё это «совсем» изолировать от «/», кроме как чтобы не «писать на диск». А чуть менее чем во всех актуальных unix-ах есть tmpfs и/или O_TMPFILE и т.п.

Deleted
()
Ответ на: комментарий от ls-h
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>


int main(void)
{
	int fd = shm_open("qwerty", O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
	if (fd == -1) {
		perror("shm_open failed");
		return 1;
	}

	pid_t pid = getpid();
	printf("my process id is %d\n", pid);

	char link_path[PATH_MAX];
	snprintf(link_path, PATH_MAX, "/proc/%d/fd/%d", pid, fd);
	printf("link_path: %s\n", link_path);

	char real_path[PATH_MAX];
	if (readlink(link_path, real_path, PATH_MAX)== -1) {
		perror("readlink failed");
		return 1;
	}
	printf("real_path: %s\n", real_path);

	write(fd, "hello", 5);

	for (;;) {
		lseek(fd, 0, SEEK_SET);
		char buf[200];
		ssize_t len = read(fd, buf, 200);
		buf[len] = 0;
		puts(buf);
		sleep(1);
	}

	return 0;
}

Оно тебе выведет два пути, попробуй в каждый чего нибудь записать, может въедешь.

Конпелять cc -lrt file.c

anonymous
()
Ответ на: комментарий от anonymous
ssize_t len = read(fd, buf, 200-1);

исправил

anonymous
()
Ответ на: комментарий от ls-h

https://stackoverflow.com/questions/56077527/why-shm-open-succeeds-even-there...
Ссылку не донёс...

Посмотрел, там начинается чорная магия по подбору альтернативной директории. Тоесть в любом случае либо где-то в файловой системе создастся файл, либо shm_open вернёт ошибку.

Можешь интереса ради запустить прогу выше через strace, потом размонтировать и удалить /dev/shm (в виртуалке, ага), запустить опять и сравнить.

anonymous
()
Ответ на: комментарий от anonymous

Созданный через memfd_create файл ты можешь открыть в паралельном процессе, о чем явно сказано в манах.

memfd_create поддерживается только linux 3.17+.

byko3y ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.