原文地址:https://zhuanlan.zhihu.com/p/39925519
温故之.NET进程间通信——内存映射文件
骨灰级程序猿上一篇技术文章中,我们讲解了进程间通信中的管道通信方式,这只是多种进程间通信方式中的一种,这篇文章我们回顾一下另一种进程间通信的方式——内存映射文件
基础概念
Windows
提供了 3 种进行内存管理的方法:
- 虚拟内存:适合用来管理大型对象或结构数组
- 内存映射文件:适合用来管理大型数据流(通常来自文件),也适合在单机上多个进程(运行着的进程)之间共享数据
- 内存堆栈:适合用来管理大量的小对象
内存映射文件在
Windows
中使用场景很多,进程间通信也只是其多个应用场景中的一个。它在操作大文件时非常高效,这种场景下也使用得非常广泛。比如数据库文件借助文件和内存空间之间的这种映射,应用可以直接对内存执行读写操作,从而间接的修改文件。自
.NET Framework 4
起(在System.IO.MemoryMappedFiles
命名空间下),我们便可以通过托管代码去访问内存映射文件如果我们需要使用内存映射文件,则必须创建该内存映射文件的视图(该视图映射到文件的全部内存或一部分内存上)。我们也可以为内存映射文件的同一部分创建多个视图,从而创建并发内存。若要让两个视图一直处于并发状态,必须通过同一个内存映射文件创建它们。当文件大于可用于内存映射的应用逻辑内存空间(在
32
位计算机中为2GB
)时,也有必要使用多个视图视图分为以下两种类型:流访问视图和随机访问视图
- 使用流访问视图,可以顺序访问文件。建议对非持久化文件和
IPC
使用这种类型(通过MemoryMappedFile.CreateViewStream
创建此视图)- 随机访问视图是处理持久化文件的首选类型(通过
MemoryMappedFile.CreateViewAccessor
创建此视图)内存映射文件通过操作系统的内存管理程序进行访问,因此文件会被自动分区到很多页面,并根据需要进行访问(即自动的内存管理,不需要我们人为干预)
内存映射文件分为两种类型:持久化内存映射文件和非持久化内存映射文件,不同的类型应用于不同的场景
持久化内存映射文件
持久化文件是与磁盘上的源文件相关联的内存映射文件(即磁盘上需要有个文件才行)。当最后一个进程处理完文件时,数据保存到磁盘上的源文件中。此类内存映射文件适用于处理非常大的源文件,这种方式在很多数据库中都有使用
可使用
MemoryMappedFile.CreateFromFile
创建此类型的映射文件。要想访问此类型的映射文件,可通过MemoryMappedFile.CreateViewAccessor
创建一个随机访问视图。这也是访问持久化内存映射文件推荐的方式示例代码如下
using System; using System.IO; using System.IO.MemoryMappedFiles; using System.Runtime.InteropServices; namespace App { class Program { static void Main(string[] args) { long offset = 0x0000; long length = 0x2000; // 8K string mapName = "Demos.MapFiles.TestInstance"; int colorSize = Marshal.SizeOf(typeof(Color)); long number = length / colorSize; Color color; // 从磁盘上现有文件,创建内存映射文件,第三个参数为这个内存映射文件的名称 var firstMapFile = MemoryMappedFile.CreateFromFile(@"d:\test_data.data", FileMode.OpenOrCreate, mapName); // 创建一个随机访问视图 using (var accessor = firstMapFile.CreateViewAccessor(offset, length)) { // 更改映射文件内容 for (long i = 0; i < number; i += colorSize) { accessor.Read(i, out color); color.Add(new Color() { R = 10, G = 10, B = 10, A = 10 }); accessor.Write(i, ref color); } } // 打开已经存在的内存映射文件 // 第一个参数为这个内存映射文件的名称 // 【此处的代码可以放在另一个进程中】 var secondMapFile = MemoryMappedFile.OpenExisting(mapName); using (var secondAccessor = secondMapFile.CreateViewAccessor(offset, length)) { // 读取映射文件内容 for (long i = 0; i < number; i += colorSize) { secondAccessor.Read(i, out color); Console.WriteLine(color); } } Console.ReadLine(); // 释放内存映射文件资源 firstMapFile.Dispose(); secondMapFile.Dispose(); }