可以在两个独立的进程之间共享内存中的数据吗?

我有一个使用 Twsted 的 xmlrpc 服务器。服务器有大量的数据存储在内存中。有没有可能运行一个辅助的、独立的 xmlrpc 服务器,它可以访问第一个服务器中的内存中的对象?

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

* 编辑 *

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

68858 次浏览
mmap.mmap(0, 65536, 'GlobalSharedMemory')

我认为对于希望共享相同内存的所有进程来说,标记(“ GlobalSharedMemory”)必须是相同的。

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

如果不对 Python 核心运行时进行一些深度和暗度的重写(以允许强制使用给定的共享内存段的分配器,并确保不同进程之间的兼容地址) ,就没有办法在任何一般意义上“共享内存中的对象”。这个列表将包含100万个元组地址,每个元组由其所有项目的地址组成,而且每个地址都将由 pymalloc 分配,这种分配方式不可避免地会因进程而异,并在堆中传播。

在除 Windows 之外的几乎所有系统上,都有可能产生一个子进程,该子进程对父进程空间中的对象基本上具有只读访问权... ... 只要父进程不更改这些对象。这是通过调用 os.fork()获得的,在实践中“快照”当前进程的所有内存空间,并在复制/快照上启动另一个同时进程。在所有的现代操作系统中,这实际上是非常快的,这要归功于一种“写入时拷贝”的方法: 在 fork 之后任何一个进程都没有修改的虚拟内存页面不会被真正复制(访问相同的页面是共享的) ; 一旦任何一个进程修改了之前共享的页面中的任何位,噗,该页面被复制,页表被修改,所以修改过程现在有了自己的拷贝,而另一个进程仍然看到原来的页面。

这种极其有限的共享形式在某些情况下仍然可以成为救命稻草(尽管它极其有限: 例如,记住,由于引用计数,添加对共享对象的引用计算为“改变”该对象,因此将强制页面复制!)当然,除了 Windows 系统,那里没有。除了这一个例外(我不认为它会涵盖你的用例) ,共享包含对其他对象的引用/指针的对象图基本上是不可行的——现代语言中几乎所有感兴趣的对象集(包括 Python)都属于这种分类。

在极端(但足够简单)的情况下,可以通过放弃这种对象图的本机内存表示来获得共享。例如,一个包含100万个元组的列表,每个元组有16个浮点数,实际上可以表示为一个128MB 的共享内存块——所有16M 的浮点数都以双精度 IEEE 表示方式端对端地放置——顶部有一个小的垫片,以“让它看起来像”你正在以正常的方式处理事情(当然,毕竟不是那么小的垫片也必须处理必然会出现的极其棘手的进程间同步问题; ——)。从那以后事情只会越来越复杂。

现代的并发方法越来越轻视共享-任何方法都支持无共享的方法,即任务通过消息传递进行通信(即使在使用线程和共享地址空间的多核系统中,当大量内存区域被多核同时修改时,硬件在缓存、管道延迟等方面带来的同步问题和性能影响正在把人们推开)。

例如,Python 标准库中的多处理模块主要依赖于 pickle 和来回发送对象,而不是共享内存(当然不是以 R/W 的方式!-).

我知道这对 OP 来说不是什么好消息,但是如果他确实需要让多个处理器工作,他最好考虑让他们必须共享的任何东西驻留在可以通过消息传递访问和修改的地方——一个数据库,一个 memcache 集群,一个专门的进程,只是把这些数据保存在内存中,并根据请求发送和接收它们,以及其他以消息传递为中心的架构。

为什么不直接使用数据库来共享数据呢?您有许多轻量级选项,不需要担心并发性问题: sqlite、任何 nosql/key-value 数据库类型,等等。

为什么不把共享数据放到 memcache 服务器上呢? 这样两个服务器都可以很容易地访问它。

您可以编写一个 C 库来创建和操作针对特定用途的共享内存数组,然后使用 ctype 从 Python 访问它们。

或者,将它们放在/dev/shm (tmpfs)中的文件系统中。您可以节省大量开发工作,但性能开销却很小: 从 tmpfs 文件系统读/写只比 memcpy 大一点点。

在 Python 中,有几个第三方库可用于低级共享内存操作:

  • Sysv _ ipc
    • > 对于 posx 不符合要求的系统
  • Posx _ ipc
    • > 使用 Cygwin 在 Windows 中工作

这两个都是可用的 通过 Pip

[1]另一个软件包 可以使用,但是 不赞成不行。参见 这一页对这些库进行比较。

C 到 Python 通信的示例代码 马丁 · 欧汉伦:

作家

#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);
}

碎纸机

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

其实很简单。您可以只使用共享内存。这个示例在 C + + 中创建了一个元组列表(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)

如果您的数据仅仅是元组,并且您愿意将这些数据作为

  • (nrows x tuplewidth) np.ndarray,或
  • N 1d np.ndarrays

那么我强烈推荐在 Memmap中使用 numpy 的包装器。

我的理解是:

  • 将 numpy 数组保存为一个包含原始数组内容的平面 memmap 文件
  • 每个进程将一个 ndarray 指向 memmap 文件作为其后台数据。

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

由于 memmap 使用分页来加载数据,因此从磁盘访问大型数据集的速度极快。事实上,我认为现代操作系统将数据从磁盘加载到内存的速度没有比这更快的了——不涉及序列化。