.Net多进程通信共享内存映射文件Memory Mapped的攻略
什么是Memory Mapped文件
Memory Mapped文件是一种通信机制,可以在多个进程之间共享数据,同时不需要进行大规模的内存拷贝,这个机制的核心是共享内存映射文件。
在Windows系统中,每个进程都有自己独立的虚拟内存空间,不同进程之间的虚拟内存空间是隔离的。但实际上,操作系统内部实现了一个内存映射机制(memory mapping),将虚拟内存地址和物理内存地址进行映射,实现了虚拟内存到物理内存的转换。利用这一机制,我们可以将同一个共享内存映射文件映射到多个进程的虚拟内存地址空间中。这样,不同进程就可以直接访问同一个内存区域,实现数据共享。
.Net中实现Memory Mapped文件
在.Net框架中,实现共享内存映射文件的方式就是MemoryMappedFile类。该类通过一些静态方法创建和打开内存映射文件,然后通过一些实例方法获取映射视图,从而访问共享内存。
创建内存映射文件
创建内存映射文件可以使用MemoryMappedFile类的CreateNew方法,这个方法会创建一个新的内存映射文件,如果文件已存在则会抛出异常。
using System.IO.MemoryMappedFiles;
using System.IO;
try
{
using (var mmf = MemoryMappedFile.CreateNew("test", 1024))
{
// 对读写权限的定义,需要进行类型转换
using (var accessor = mmf.CreateViewAccessor(0, 1024, MemoryMappedFileAccess.ReadWrite))
{
// 将数据写入共享内存中
accessor.Write(1, (byte)65); //写入A,byte类型
accessor.Write(2, (byte)66); //写入B,byte类型
}
}
}
catch(IOException ex)
{
//文件已存在
}
这个示例中创建了一个名为“test”的共享内存映射文件,大小为1024字节。使用CreateViewAccessor方法,可以获取到一个访问共享内存的对象,然后使用Write方法往共享内存中写入数据。
打开内存映射文件
如果内存映射文件已经存在,并且需要打开它来进行操作,可以使用MemoryMappedFile类的OpenExisting方法。这个方法会打开指定名称的内存映射文件,如果文件不存在则会抛出异常。
using System.IO.MemoryMappedFiles;
try
{
using (var mmf = MemoryMappedFile.OpenExisting("test"))
{
// 对读写权限的定义,需要进行类型转换
using (var accessor = mmf.CreateViewAccessor(0, 1024, MemoryMappedFileAccess.ReadWrite))
{
// 从共享内存中读取数据
var chA = (char)accessor.ReadByte(1); //读取A
var chB = (char)accessor.ReadByte(2); //读取B
}
}
}
catch (FileNotFoundException ex)
{
//文件不存在
}
这个示例中打开了名称为“test”的共享内存映射文件,并从中读取数据。
示例1:实现两个进程之间的数据通信
在这个示例中,我们将创建两个不同的控制台应用程序,一个用来写入数据,另一个用来读取数据。这两个程序会通过一个内存映射文件来实现数据通信。
写入数据
using System.IO.MemoryMappedFiles;
using System.Threading;
class Program
{
static void Main(string[] args)
{
try
{
// 当读取进程打开内存映射文件后,此处才执行
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("test"))
{
Console.WriteLine("Successfully opened test");
//对读写权限的定义,需要进行类型转换
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(0, 1024, MemoryMappedFileAccess.ReadWrite))
{
for (int i = 1; i <= 10; i++)
{
accessor.Write(i, (byte)i);
Console.WriteLine("Value {0} written", i);
Thread.Sleep(1000);
}
}
}
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Memory-mapped file does not exist. Run Process A first, then B.");
}
}
}
这个程序将打开一个名为“test”的共享内存映射文件,并循环向文件中写入数据。
读取数据
using System;
using System.IO.MemoryMappedFiles;
using System.Threading;
class Program
{
static void Main(string[] args)
{
try
{
using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen("test", 1024))
{
Console.WriteLine("Successfully created test");
//对读写权限的定义,需要进行类型转换
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(0, 1024, MemoryMappedFileAccess.ReadWrite))
{
for (int i = 1; i <= 10; i++)
{
byte value;
accessor.Read(i, out value);
Console.WriteLine("Value read {0}", value);
Thread.Sleep(1000);
}
}
}
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Memory-mapped file does not exist. Run Process A first, then B.");
}
}
}
这个程序将创建一个名为“test”的共享内存映射文件,并循环从文件中读取数据。
示例2:实现一个基于共享内存的循环队列
在这个示例中,我们将创建一个简单的循环队列并利用共享内存实现两个进程之间的数据队列操作。
实现共享内存队列
class SharedQueue
{
// 队列最大长度
public const int QueueLength = 8;
// 共享内存映射对象
private readonly MemoryMappedFile mmf;
// 用于控制共享内存区域的访问权限
private readonly Mutex mtx;
// 读写指针
private int readIndex = 0, writeIndex = 0;
// 数据区域
private readonly int[] data = new int[QueueLength];
// 读指针属性
public int ReadIndex
{
get
{
return readIndex;
}
}
// 写指针属性
public int WriteIndex
{
get
{
return writeIndex;
}
}
// 构造函数,初始化内存映射文件和互斥量
public SharedQueue()
{
// 创建或打开内存映射文件
mmf = MemoryMappedFile.CreateOrOpen("SharedQueueMemoryMappedFile", data.Length * sizeof(int));
// 创建互斥量
mtx = new Mutex(false, "SharedQueueMutex");
}
// 向队列中添加一个元素
public void Add(int value)
{
mtx.WaitOne();
bool indexChanged = false;
// 循环队列
if ((writeIndex + 1) % QueueLength != readIndex)
{
data[writeIndex] = value;
writeIndex = (writeIndex + 1) % QueueLength;
indexChanged = true;
}
mtx.ReleaseMutex();
// 如果写索引改变就通知读线程有数据更新
if (indexChanged)
{
NotifyUpdate();
}
}
// 从队列中取出一个元素
public int Get()
{
mtx.WaitOne();
bool indexChanged = false;
int value = -1;
// 判断队列是否为空
if (readIndex != writeIndex)
{
value = data[readIndex];
readIndex = (readIndex + 1) % QueueLength;
indexChanged = true;
}
mtx.ReleaseMutex();
// 如果读索引改变就通知写线程有数据更新
if (indexChanged)
{
NotifyUpdate();
}
return value;
}
// 查询队列中是否有数据
public bool HasData()
{
bool result;
mtx.WaitOne();
result = readIndex != writeIndex;
mtx.ReleaseMutex();
return result;
}
// 从共享内存中获取读写索引
public void GetIndex(out int readIndex, out int writeIndex)
{
readIndex = 0;
writeIndex = 0;
mtx.WaitOne();
readIndex = this.readIndex;
writeIndex = this.writeIndex;
mtx.ReleaseMutex();
}
// 通知队列更新
private void NotifyUpdate()
{
//打开另一个进程的Mutex对象
using (var mutex = Mutex.OpenExisting("SharedQueueMutex"))
{
//通知队列更新
mutex.ReleaseMutex();
}
}
}
写进程代码
using System.Threading;
class Producer
{
static void Main(string[] args)
{
//创建共享内存队列
var queue = new SharedQueue();
while (true)
{
for (int i = 0; i < SharedQueue.QueueLength; i++)
{
if (queue.HasData())
{
// 如果队列中有数据,就先等待一会
Thread.Sleep(10);
continue;
}
// 向队列中添加一个元素
queue.Add(i + 1);
Thread.Sleep(1000);
}
}
}
}
读进程代码
using System;
using System.Threading;
class Consumer
{
static void Main(string[] args)
{
//创建共享内存队列
var queue = new SharedQueue();
while (true)
{
int readIndex, writeIndex;
queue.GetIndex(out readIndex, out writeIndex);
while (queue.HasData())
{
// 从队列中获取一个元素,并显示到控制台上
var value = queue.Get();
Console.WriteLine("Got value: {0}", value);
}
Thread.Sleep(1000);
}
}
}
本例中创建了一个名为“SharedQueueMemoryMappedFile”的共享内存映射文件,并创建一个“SharedQueueMutex”的命名互斥量,用于控制进程对共享内存区域的访问权限。然后实现了一个基于共享内存的循环队列。
在生产者进程中,实例化共享队列对象,并在循环中向队列中添加数据。在消费者进程中也同样实例化共享队列对象,并在循环中获取队列中的数据。在获取队列的元素时,需要先获取队列中的读写索引,防止读写指针改变带来的问题。
这是一个基本的实现,可以修改和扩展以满足特定的应用需求。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:.Net多进程通信共享内存映射文件Memory Mapped - Python技术站