上一篇技术文章中,咱们讲解了进程间通讯中的管道通讯方式,这只是多种进程间通讯方式中的一种,这篇文章咱们回顾一下另外一种进程间通讯的方式——内存映射文件数据库
Windows
提供了 3 种进行内存管理的方法:数组
内存映射文件在 Windows
中使用场景不少,进程间通讯也只是其多个应用场景中的一个。它在操做大文件时很是高效,这种场景下也使用得很是普遍。好比数据库文件安全
借助文件和内存空间之间的这种映射,应用能够直接对内存执行读写操做,从而间接的修改文件。自 .NET Framework 4
起(在 System.IO.MemoryMappedFiles
命名空间下),咱们即可以经过托管代码去访问内存映射文件bash
若是咱们须要使用内存映射文件,则必须建立该内存映射文件的视图(该视图映射到文件的所有内存或一部份内存上)。咱们也能够为内存映射文件的同一部分建立多个视图,从而建立并发内存。若要让两个视图一直处于并发状态,必须经过同一个内存映射文件建立它们。当文件大于可用于内存映射的应用逻辑内存空间(在 32
位计算机中为 2GB
)时,也有必要使用多个视图并发
视图分为如下两种类型:流访问视图和随机访问视图app
IPC
使用这种类型(经过 MemoryMappedFile.CreateViewStream
建立此视图)MemoryMappedFile.CreateViewAccessor
建立此视图)内存映射文件经过操做系统的内存管理程序进行访问,所以文件会被自动分区到不少页面,并根据须要进行访问(即自动的内存管理,不须要咱们人为干预)ide
内存映射文件分为两种类型:持久化内存映射文件和非持久化内存映射文件,不一样的类型应用于不一样的场景工具
持久化文件是与磁盘上的源文件相关联的内存映射文件(即磁盘上须要有个文件才行)。当最后一个进程处理完文件时,数据保存到磁盘上的源文件中。此类内存映射文件适用于处理很是大的源文件,这种方式在不少数据库中都有使用学习
可以使用 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();
}
}
// 为了便于测试,建立一个简单的结构
public struct Color {
public byte R, G, B, A;
public void Add(Color color) {
this.R = (byte)(this.R + color.R);
this.G = (byte)(this.G + color.G);
this.B = (byte)(this.B + color.B);
this.A = (byte)(this.A + color.A);
}
public override string ToString() {
return $"Color({R},{G},{B},{A})";
}
}
}
复制代码
以上示例可多运行几回,就能发现输出的颜色值的变化
非持久化文件是不与磁盘上的文件相关联的内存映射文件(即磁盘上没有对应的文件,这里的文件咱们是看不见的)。当最后一个进程处理完文件时,数据会丢失,且文件被垃圾回收器回收。此类文件适合建立共享内存,以进行进程间通讯
可以使用 MemoryMappedFile.CreateNew
或 MemoryMappedFile.CreateOrOpen
建立此类型的映射文件。访问此种类型的映射文件,推荐使用方法 MemoryMappedFile.CreateViewStream
来建立一个流访问视图,它能够实现顺序访问文件
这种方式的示例代码会在下面的 使用内存映射文件实现进程间通讯 小节给出
要实现进程间通讯,单个进程须要映射到相同的内存映射文件,并使用相同的内存映射文件名称。为了保证共享数据的安全,每每咱们须要借助 Mutex
或者其余的互斥信号来对共享内存区域进行读写的控制
进程 A 示例代码以下
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
namespace App {
class Program {
static void Main(string[] args) {
// 此处的 MemoryMappedFile 实例不能使用 using 语法
// 由于它会自动释放咱们的内存映射文件,会致使进程B找不到这个映射文件而抛出异常
MemoryMappedFile mmf = MemoryMappedFile.CreateNew("IPC_MAP", 10000);
// 建立互斥量以协调数据的读写
Mutex mutex = new Mutex(true, "IPC_MAP_MUTEX", out bool mutexCreated);
using (MemoryMappedViewStream stream = mmf.CreateViewStream()) {
StreamWriter sw = new StreamWriter(stream);
// 向内存映射文件种写入数据
sw.WriteLine("This is IPC MAP TEXT");
// 这一句是必须的,在某些状况下,若是不调用Flush 方法会形成进程B读取不到数据
// 它的做用是当即写入数据
// 这样在此进程释放 Mutex 的时候,进程B就能正确读取数据了
sw.Flush();
}
mutex.ReleaseMutex();
Console.ReadLine();
mmf.Dispose();
}
}
}
复制代码
进程 B 示例代码以下
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
namespace App {
class Program {
static void Main(string[] args) {
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("IPC_MAP")) {
Mutex mutex = Mutex.OpenExisting("IPC_MAP_MUTEX");
// 等待写入完成
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream()) {
StreamReader sr = new StreamReader(stream);
// 读取进程 A 写入的内容
Console.WriteLine(sr.ReadLine());
}
mutex.ReleaseMutex();
}
Console.ReadLine();
}
}
}
复制代码
这儿咱们须要先运行示例 A 以启动进程 A,再运行示例 B 启动进程 B。进程 B 输出为
This is IPC MAP TEXT
复制代码
表示成功读取到了进程 A 写入的数据
这种方式在一个主进程,多个从进程之间通讯会很是的方便,不但稳定并且快速。而且,这种方式相比于其余的进程间通讯方式,效率是最高的。所以这种方式在单机中多个从进程间的通讯采用得最多
对于一些比较复杂的进程间通讯,若是须要传递大量的不一样类型的数据,咱们可使用序列化的方式将须要传递的对象序列化。好比咱们能够采用如下工具对传递的数据序列化:Protobuf
、Jil
、MsgPack
等。这三种序列化库是目前市面上比较快的,固然咱们也能够根据项目的实际状况来选择合适的库
关于内存映射文件,咱们还须要了解如下几点
MemoryMappedFile.CreateFromFile
方法时若是不指定文件容量,那么,建立的内存映射文件的容量等同于文件的大小MemoryMappedFile.CreateFromFile
的 capacity
参数)MemoryMappedFile
对象时,咱们应该及时地调用 Dispose
方法释放它占有的资源(进程结束后,其资源也会被释放,但咱们应该养成良好的习惯,主动释放)