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

76 投票
10 回答
71413 浏览
提问于 2025-04-15 13:33

我有一个使用Twisted的xmlrpc服务器。这个服务器在内存中存储了大量的数据。请问有没有可能让一个独立的xmlrpc服务器运行,并且可以访问第一个服务器中内存里的对象?

也就是说,serverA启动后创建了一个对象,然后serverB启动后可以读取serverA中的这个对象。

* 编辑 *

要共享的数据是一个包含100万个元组的列表。

10 个回答

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
18
mmap.mmap(0, 65536, 'GlobalSharedMemory')

我觉得这个标签("GlobalSharedMemory")对于所有想要共享同一块内存的进程来说,必须是一样的。

http://docs.python.org/library/mmap.html

141

如果不对Python的核心运行时进行复杂的修改(比如强制使用某个共享内存的分配器,并确保不同进程之间的地址兼容),就没有办法在一般意义上“共享内存中的对象”。想象一下,一个列表里存着一百万个元组的地址,每个元组又由它所有项的地址组成,而这些地址是由pymalloc分配的,分配方式在不同进程中会有所不同,并且会分散在内存的各个地方。

在几乎所有系统中,除了Windows,可以创建一个子进程,它基本上可以只读访问父进程中的对象……前提是父进程也不改变这些对象。这是通过调用os.fork()实现的,实际上它会“快照”当前进程的所有内存空间,并在这个快照上启动另一个同时进行的进程。在所有现代操作系统中,这个过程非常快速,因为采用了“写时复制”的方法:在fork之后,如果两个进程都没有修改某些虚拟内存页面,这些页面并不会真正被复制(而是共享同一页面);一旦任一进程修改了之前共享的页面中的任何内容,哗啦,那一页就会被复制,页面表也会被修改,这样修改的进程就有了自己的副本,而另一个进程仍然看到原来的内容。

这种非常有限的共享方式在某些情况下仍然可以派上用场(虽然它的限制非常大:比如说,给一个共享对象增加引用就算是“修改”了这个对象,因为引用计数的原因,这会导致页面被复制!)……当然,Windows上是没有这个功能的。除了这个例外(我觉得这不适合你的使用场景),共享包含对其他对象的引用或指针的对象图基本上是不可行的——而现代语言(包括Python)中几乎所有感兴趣的对象都属于这个分类。

在极端(但足够简单)的情况下,可以通过放弃这种对象图的本地内存表示来实现共享。例如,一个包含一百万个元组的列表,每个元组有十六个浮点数,实际上可以表示为一个128MB的共享内存块——所有的1600万浮点数以双精度IEEE格式依次排列——上面再加一点小工具来“让它看起来”像是以正常方式访问(当然,这个小工具还得处理那些复杂的进程间同步问题,这些问题肯定会出现;-)。从这里开始,事情只会变得更加复杂。

现代的并发处理方法越来越不喜欢共享任何东西,而是倾向于共享无物的方法,任务通过消息传递进行沟通(即使在使用线程和共享地址空间的多核系统中,当多个核心同时修改大块内存时,带来的同步问题和硬件性能损失,比如缓存、流水线停顿等,都会让人们远离这种方式)。

例如,Python标准库中的multiprocessing模块主要依赖于序列化和对象的传递,而不是共享内存(当然不是以读写的方式!)。

我知道这对提问者来说并不是好消息,但如果他确实需要让多个处理器工作,最好考虑让他们共享的内容放在可以通过消息传递访问和修改的地方——比如数据库、内存缓存集群、专门的进程(只负责将数据保存在内存中并根据请求发送和接收数据)以及其他以消息传递为中心的架构。

撰写回答