上一篇技術文章中,咱們講解了進程間通訊中的管道通訊方式,這只是多種進程間通訊方式中的一種,這篇文章咱們回顧一下另外一種進程間通訊的方式——內存映射文件數據庫
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
方法釋放它佔有的資源(進程結束後,其資源也會被釋放,但咱們應該養成良好的習慣,主動釋放)