两个独立的进程之间是否可以共享内存数据?

74

我有一个使用Twisted的xmlrpc服务器。 服务器存储着大量数据的内存。是否可能运行第二个独立的xmlrpc服务器,并能访问第一个服务器中的内存对象?

因此,serverA启动并创建一个对象。 serverB启动并可以从serverA中读取对象。

* 编辑 *

要共享的数据是1百万个元组的列表。

10个回答

139

如果不对Python核心运行时进行深度和黑暗的重写(以允许强制使用给定共享内存段的分配器并确保不同进程之间的兼容地址),则无法在一般意义上“共享内存中的对象”。那个列表将保存一百万个元组的地址,每个元组由其所有项的地址组成,这些地址都必须由pymalloc分配,在各个进程之间必然会有所变化,并且在整个堆中扩散。

除Windows外,几乎在所有系统上,可以生成一个子进程,该子进程基本上具有只读访问父进程空间中的对象...只要父进程不修改这些对象。通过调用os.fork()来实现这一点,在实践中,“快照”当前进程的所有内存空间,并在副本/快照上启动另一个同时进程。在所有现代操作系统上,这实际上非常快,因为采用了“写时复制”的方法:在fork后,未被任何进程修改的虚拟内存页面不会真正复制(相反,两个进程都访问同一页面); 只要任一进程修改先前共享的页面中的任何位,poof,该页面就会被复制,并修改页表,因此修改进程现在具有自己的副本,而其他进程仍然看到原始进程。

这种极其有限的共享形式在某些情况下仍然可能挽救生命(尽管它非常有限:例如,请记住将引用添加到共享对象中会计为修改该对象,由于引用计数,因此会强制进行页面复制!)...当然,在Windows上除外,因为它不可用。除此之外(我认为不适用于您的用例),包括指向其他对象的引用/指针的对象图的共享基本上是不可行的 - 并且在现代语言(包括Python)中任何感兴趣的对象集都属于此类别。

在极端(但足够简单)的情况下,可以通过放弃对象图的本地内存表示来实现共享。例如,一个包含一百万个元组的列表,每个元组都有十六个浮点数,实际上可以表示为一个大小为128MB的共享内存块——所有16M个双精度IEEE表示的浮点数都排成一行,顶部还有一个小的“垫片”,以“使其看起来”像是正常访问对象(当然,这个不太小的垫片还必须处理极其复杂的跨进程同步问题;-))。从那里开始,情况只会变得更加棘手和复杂。
现代并发的方法越来越倾向于使用共享无关的方法,而不是共享任何东西,任务通过消息传递进行通信(即使在使用多线程和共享地址空间的多核系统中,当大量的内存区域被多个核同时活动修改时,同步问题和硬件性能损失,如缓存、流水线停顿等,也在迫使人们回避共享)。
例如,在Python标准库中的多进程模块主要依赖于数据的序列化和发送,而不是共享内存(当然不是以读/写的方式!)。
我意识到这对OP来说可能不是好消息,但如果他确实需要让多个处理器工作,最好考虑让它们必须共享的任何东西驻留在可以通过消息传递访问和修改的地方——数据库、memcache集群、专用进程只需将这些数据保持在内存中并根据请求发送和接收等基于消息传递的架构。

3
多谢您的精彩回答,Alex。但在某些场景下,需要在进程间共享数据以实现最大化访问速度和最小化内存消耗。本帖提出了使用multiprocess.Array方法来实现共享内存。https://dev59.com/KWgv5IYBdhLWcg3wSewe - RayLuo
Alex,你为什么说:“(我认为这不会涵盖您的用例)”?这是因为引用计数吗? - max
@max,不仅如此,还因为只有只读访问权限(这也意味着“没有新的引用”),以及无Windows限制。 - Alex Martelli
1
有没有办法禁用某些Python对象的引用计数?毕竟,如果它们从未被拷贝到子进程中,我并不需要垃圾回收。 - max
@max,对的 - 在CPython中,引用计数始终处于启用状态(在其他实现中,如Jython中,您将努力关闭更复杂的垃圾回收形式)。 - Alex Martelli
显示剩余3条评论

18

1
这可能取决于操作系统。它在Windows上可以工作,我刚刚在两个进程之间尝试过了。 - Joe Koberg
共享对象比共享内存更难。 也许multiprocessing模块可以提供一些关于跨进程数据共享的见解。 - Joe Koberg
2
我想你需要使用pickle(或类似的编码方式)来编码任何你想要分享的东西。 - David Z
1
但是反序列化将在您自己的内存中产生对象的副本...原帖作者必须让我们知道要共享哪些类型的对象! - Joe Koberg
这个只在Windows上有效。在Linux上无法使用tagname。 - undefined

12

有一些第三方库可用于Python中的低级共享内存操作:

  • sysv_ipc
    • > 适用于不符合posix标准的系统
  • posix_ipc
    • > 在Windows上可以使用cygwin

这两个库都可以通过pip获取。

[1] 还有另一个shm包,但已经弃用。请参考此页面进行库的比较。

示例代码用于C与Python通信由Martin O'Hanlon提供:

shmwriter.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main(int argc, const char **argv)
{
   int shmid;
   // give your shared memory an id, anything will do
   key_t key = 123456;
   char *shared_memory;

   // Setup shared memory, 11 is the size
   if ((shmid = shmget(key, 11, IPC_CREAT | 0666)) < 0)
   {
      printf("Error getting shared memory id");
      exit(1);
   }
   // Attached shared memory
   if ((shared_memory = shmat(shmid, NULL, 0)) == (char *) -1)
   {
      printf("Error attaching shared memory id");
      exit(1);
   }
   // copy "hello world" to shared memory
   memcpy(shared_memory, "Hello World", sizeof("Hello World"));
   // sleep so there is enough time to run the reader!
   sleep(10);
   // Detach and remove shared memory
   shmdt(shmid);
   shmctl(shmid, IPC_RMID, NULL);
}

shmreader.py

import sysv_ipc

# Create shared memory object
memory = sysv_ipc.SharedMemory(123456)

# Read value from shared memory
memory_value = memory.read()

# Find the 'end' of the string and strip
i = memory_value.find('\0')
if i != -1:
    memory_value = memory_value[:i]

print memory_value

当然,你仍然需要以某种方式在共享内存中表示对象。 - DylanYoung
这里是否有一种方法可以使用名称而不是整数键来使用共享内存? - Shir
@Shir "key必须为None、IPC_PRIVATE或大于0且小于等于KEY_MAX的整数。如果key为None,则模块会选择一个随机未使用的key。"
  • http://semanchuk.com/philip/sysv_ipc/
- DylanYoung
但是你可以创建一个函数,将字符串确定性地映射到整数。只要在需要访问共享内存的两个位置都有等效的函数可用,您就可以使用它从字符串生成密钥。 - DylanYoung

11

无关进程不可能实现此功能。 - raphaelauv
独立进程无法工作。为什么会有这么多赞? - Yin
独立进程无法工作。为什么会有这么多赞?+1 - 1943 Yonv

4
你可以编写一个C库来创建和操作共享内存数组,以实现你的特定目的,并使用ctypes从Python访问它们。
或者,将它们放在/dev/shm(即tmpfs)文件系统中。这样做可以节省大量开发工作,而性能开销却很小:从tmpfs文件系统读/写仅仅是一个memcpy操作。

3

其实很简单。你可以使用共享内存。这个例子在C++中创建了一个元组列表 (python),并将其与一个Python进程共享,以便Python进程可以使用这个元组列表。如果要在两个Python进程之间使用,只需在发送进程上将访问权限设为ACCESS_WRITE,然后调用write方法。

C++(发送进程):

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include <iostream>
#include <string>

#define BUF_SIZE 256
TCHAR szName[]=TEXT("Global\\MyFileMappingObject");
TCHAR szMsg[]=TEXT("[(1, 2, 3), ('a', 'b', 'c', 'd', 'e'), (True, False), 'qwerty']");

int _tmain(int argc, _TCHAR* argv[])
{
     HANDLE hMapFile;
   LPCTSTR pBuf;

   hMapFile = CreateFileMapping(
                 INVALID_HANDLE_VALUE,    // use paging file
                 NULL,                    // default security
                 PAGE_READWRITE,          // read/write access
                 0,                       // maximum object size (high-order DWORD)
                 BUF_SIZE,                // maximum object size (low-order DWORD)
                 szName);                 // name of mapping object

   if (hMapFile == NULL)
   {
      _tprintf(TEXT("Could not create file mapping object (%d).\n"),
             GetLastError());
      return 1;
   }
   pBuf = (LPTSTR) MapViewOfFile(hMapFile,   // handle to map object
                        FILE_MAP_ALL_ACCESS, // read/write permission
                        0,
                        0,
                        BUF_SIZE);

   if (pBuf == NULL)
   {
      _tprintf(TEXT("Could not map view of file (%d).\n"),
             GetLastError());

       CloseHandle(hMapFile);
       return 1;
   }

   CopyMemory((PVOID)pBuf, szMsg, (_tcslen(szMsg) * sizeof(TCHAR)));
    _getch();

   UnmapViewOfFile(pBuf);

   CloseHandle(hMapFile);
    return 0;
}

Python(接收进程):

import mmap
shmem = mmap.mmap(0,256,"Global\\MyFileMappingObject",mmap.ACCESS_READ)
msg_bytes = shmem.read()
msg_utf16 = msg_bytes.decode("utf-16")
code = msg_utf16.rstrip('\0')
yourTuple = eval(code)

2

2
多进程允许您共享原始数据数组,因此如果您可以在其中放置它,那么就很不错。 - rogerdpack

2
为什么不将共享数据放入Memcache服务器中?这样两个服务器都可以很容易地访问它。

2
memcache 可以在任何时候(例如,内存块已满)随意清除您的数据,因此有“cache”这个名称的缓存部分。如果您关心数据的持久性,请不要使用它。 - nsanders
@nsanders Redis 可以很好地工作,而且它具有一些基本对象的本地支持,而不仅仅是字符串。 - DylanYoung
1
此外,您在访问Memcache时不需要支付序列化/反序列化的成本。 - user48956
@user48956 如果你想以可预测和结构化的方式访问数据(而不需要进行大量的低级编程),我怀疑你仍然需要在共享内存中支付这个代价。 - DylanYoung
1
根据您的编程语言而定。例如,Python和numpy允许您使用共享内存来备份数据,而无需序列化。 - user48956

0

如果您的数据只是元组,并且您愿意将其作为

  • (nrows x tuplewidth) np.ndarrays,或者
  • n 1d np.ndarrays

访问,则强烈建议使用numpy的memmap包装器。

我的理解是:

  • 将numpy数组保存为持有原始数组内容的平面memmap文件
  • 每个进程将ndarray指向memmap文件作为其支持数据。文档链接显示了如何执行此操作。

这对于只读数据非常有效。 如果您想要读写,则需要使用多进程锁来保护访问。

因为memmap使用分页加载数据,所以它是从磁盘访问大型数据集的绝佳快速方式。实际上,我认为现代操作系统没有比这更快地将数据从磁盘加载到内存中的方法--不涉及序列化。


-1
为什么不直接使用数据库来处理共享数据呢?有许多轻量级的选项可供选择,您无需担心并发问题:sqlite、任何nosql/key-value数据库等。

1
你不用担心共享数据库的并发问题?请问出处 ;) - DylanYoung

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接