一、为什么要用到共享内存
进程之间交换数据我们可以通过建立本地RPC,但往往比较慢,因为要花时间去执行数据传递。
此时,如果有一个实时性要求比较高的跨进程功能,共享内存就是一个不错的选择。
1、什么是共享内存?
1、共享内存是一种在相同机器中两个正在运行的进程之间共享和传递数据的有效方式,不同进程之间共享的内存通常安排为同一段物理内存;顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存,不同进程可以同时对这段内存执行写、读的操作,这意味着高效的数据同步。
2、共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。因此如果要确保数据的准确性,我们需要采用其他机制来确保,这意味着数据不安全。
二、什么是mmap
mmap可以将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。不同进程就可以采用指针的方式读取这一段内存。
三、mmap在python中的应用
python提供了mmap库,效果同上面的mmap。
1、mmap中有哪些方法可以使
m.close()关闭 m 对应的文件;
m.find(str, start=0)从 start 下标开始,在 m 中从左往右寻找子串 str 最早出现的下标;
m.flush([offset, n])把 m 中从offset开始的n个字节刷到对应的文件中,参数 offset 要么同时指定,要么同时不指定;
m.move(dstoff, srcoff, n)等于 m[dstoff:dstoff+n] = m[srcoff:srcoff+n],把从 srcoff 开始的 n 个字节复制到从 dstoff 开始的n个字节,可能会覆盖重叠的部分。
m.read(n)返回一个字符串,从 m 对应的文件中最多读取 n 个字节,将会把 m 对应文件的位置指针向后移动;
m.read_byte()返回一个1字节长的字符串,从 m 对应的文件中读1个字节,要是已经到了EOF还调用 read_byte(),则抛出异常 ValueError;
m.readline()返回一个字符串,从 m 对应文件的当前位置到下一个’\n’,当调用 readline() 时文件位于 EOF,则返回空字符串;
m.resize(n)把 m 的长度改为 n,m 的长度和 m 对应文件的长度是独立的;
m.seek(pos, how=0)同 file 对象的 seek 操作,改变 m 对应的文件的当前位置;
m.size()返回 m 对应文件的长度(不是 m 对象的长度len(m));
m.tell()返回 m 对应文件的当前位置;
m.write(str)把 str 写到 m 对应文件的当前位置,如果从 m 对应文件的当前位置到 m 结尾剩余的空间不足len(str),则抛出 ValueError;
m.write_byte(byte)把1个字节(对应一个字符)写到 m 对应文件的当前位置,实际上 m.write_byte(ch) 等于 m.write(ch)。如果 m 对应文件的当前位置在 m 的结尾,也就是 m 对应文件的当前位置到 m 结尾剩余的空间不足1个字节,write() 抛出异常ValueError,而 write_byte() 什么都不做。
2、代码实现
# -*- coding: utf-8 -*-# 进程之间共享内存 # client1.pyimport mmapmmap_file = None# 从内存中读取信息,def read_mmap_info():global mmap_filemmap_file.seek(0)# 把二进制转换为字符串info_str = mmap_file.read().translate(None, b'\x00').decode()return info_str# 如果内存中没有对应信息,则向内存中写信息以供下次调用使用def get_mmap_info():global mmap_file# 第二个参数1024是设定的内存大小,单位:字节。如果内容较多,可以调大一点mmap_file = mmap.mmap(0, 1024, access=mmap.ACCESS_WRITE, tagname='share_mmap')return read_mmap_info()if __name__ == "__main__":result = get_mmap_info()print("====", result)
# -*- coding: utf-8 -*-# 进程之间共享内存# client2.pyimport mmapimport osmmap_file = None# 如果内存中没有对应信息,则向内存中写信息以供下次调用使用def set_mmap_info():global mmap_filemmap_file = mmap.mmap(0, 1024, access=mmap.ACCESS_WRITE, tagname='share_mmap')mmap_file.write(b"12580")# 修改内存块中的数据def reset_mmp_info():global mmap_filemmap_file.seek(0)mmap_file.write(b'100')if __name__ == "__main__":set_mmap_info()os.system("pause")reset_mmp_info()os.system("pause")
# -*- coding: utf-8 -*-# 进程之间共享内存# client3.pyimport mmapimport osmmap_file = None##如果内存中没有对应信息,则向内存中写信息以供下次调用使用def set_mmap_info():global mmap_filemmap_file = mmap.mmap(0, 1024, access=mmap.ACCESS_WRITE, tagname='share_mmap')mmap_file.write(b"2222")##修改内存块中的数据def reset_mmp_info():global mmap_filemmap_file.seek(0)mmap_file.write(b'999')if __name__ == "__main__":set_mmap_info()os.system("pause")reset_mmp_info()os.system("pause")
上述三个进程,client1为读取信息打印信息的,client2、client3为写入信息的
运行顺序为client2 -> client1 -> client3 ->client1 -> client2 -> client1 -> client3 -> client1
可以看到在client1下面会分别显示
实际上我们输入的是12580 -> 2222 -> 100 -> 999,但得到的是12580 -> 22220 -> 10020 -> 99920 因此我们可以很很清晰的得出对于内存块,不像对象那样会将对象的原数据清除而是直接在对应内存处覆盖数据。那这样实际上得到的并不是我们想要的数据。
3、如何使数据成为我们想要的数据
# -*- coding: utf-8 -*-# 进程之间共享内存# client2.pyimport mmapimport osmmap_file = None# 如果内存中没有对应信息,则向内存中写信息以供下次调用使用def set_mmap_info():global mmap_filemmap_file = mmap.mmap(0, 1024, access=mmap.ACCESS_WRITE, tagname='share_mmap')mmap_file.write(b"12580")# 修改内存块中的数据def reset_mmp_info():global mmap_filemmap_file.move(0, 10, 10)mmap_file.seek(0)mmap_file.write(b'100')if __name__ == "__main__":set_mmap_info()os.system("pause")reset_mmp_info()os.system("pause")
# -*- coding: utf-8 -*-# 进程之间共享内存# client3.pyimport mmapimport osmmap_file = None##如果内存中没有对应信息,则向内存中写信息以供下次调用使用def set_mmap_info():global mmap_filemmap_file = mmap.mmap(0, 1024, access=mmap.ACCESS_WRITE, tagname='share_mmap')mmap_file.move(0, 10, 10)mmap_file.write(b"2222")##修改内存块中的数据def reset_mmp_info():global mmap_filemmap_file.move(0, 10, 10)mmap_file.seek(0)mmap_file.write(b'999')if __name__ == "__main__":set_mmap_info()os.system("pause")reset_mmp_info()os.system("pause")
暴力的方法,将原有的一定长度的内存移动到另一个地方:mmap_file.move(0, 10, 10),此时结果就跟我们预期的一样了( 自己去验证吧=3=)
四、PRC间通信与共享内存使用及效率比较
1、rpc的使用,直接采用这篇文章上的例子《python实现RPC》
我们将上面例子稍作改动~
# RpcSever.py 改为如下import time@register_functiondef setData(data, t):print(data)cTime = time.time()print(cTime - t)return data
# RpcClient.py 改为如下import timeprint(c.setData("哈哈哈哈哈", time.time()))
先运行 RpcSever.py, 后运行RpcClient.py,可以得到如下结果:
短短的一次数据传输就会产生时间损耗,如果传输的数据量、频率再往上提升,时间的损耗更是会成倍的提高。
由此可见共享内存,在传输效率上有着其不可替代的优越性。