共有メモリは、同一メモリ上で実行されるプロセス間でデータをやりとりする場合に使用する仕組みです。
通常、プロセスで確保しているメモリは他のプロセスから参照することができないのですが、プロセス間で予め共有メモリとして使用するメモリのアドレスを共有することで、そのメモリは他のプロセスから参照可能となります。
ファイル等を介したやりとりよりも高速なため、高速化が求められる時に使用することが多いです。
目的の一つが高速化のため、データが作成され次第次々と共有する、という使い方になることが多いと思います。
今回は、そのような使い方を想定して、可変長データを繰り返し送受信するサンプルプログラムを作成しました。
書き込んだデータのサイズを伝えること、共有メモリの読み取り位置をずらしていくことがポイントとなります。
なお、今回は省略していますが、本来であれば書き込み中の読み取りを防ぐため、Mutex等の排他制御の仕組みを併用するべきです。
(参考までに、Mutexについては前回の記事で取り上げています)
【サンプルコード】
・Sender.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO.MemoryMappedFiles; namespace Sender { class Sender { // 本当はMutex等で排他制御するべき static void Main(string[] args) { // 共有メモリの作成("HelloWorld"という名前で1024バイトで定義) MemoryMappedFile mmf = MemoryMappedFile.CreateNew("HelloWorld", 1024); // 書き込みオブジェクトの生成 MemoryMappedViewAccessor mmva = mmf.CreateViewAccessor(); // 書き込みオブジェクトの現在のバイト位置 int i = 0; // 書き込み1回目 // 共有メモリの状態 // |int(オブジェクトの数)|char[](Hello )| string str1 = "Hello "; char[] data1 = str1.ToCharArray(); mmva.Write(i, data1.Length); i = i + sizeof(int); mmva.WriteArray<char>(i, data1, 0, data1.Length); i = i + sizeof(char) * data1.Length; System.Threading.Thread.Sleep(1000); // 書き込み2回目 // 共有メモリの状態 // |(1回目の内容)|int(オブジェクトの数)|char[](World)| string str2 = "World"; char[] data2 = str2.ToCharArray(); mmva.Write(i, data2.Length); i = i + sizeof(int); mmva.WriteArray<char>(i, data2, 0, data2.Length); i = i + sizeof(char) * data2.Length; System.Threading.Thread.Sleep(1000); // 書き込み3回目 // 共有メモリの状態 // |(1~2回目の内容)|int(オブジェクトの数)|char[](!)| string str3 = "!"; char[] data3 = str3.ToCharArray(); mmva.Write(i, data3.Length); i = i + sizeof(int); mmva.WriteArray<char>(i, data3, 0, data3.Length); i = i + sizeof(char) * data3.Length; System.Threading.Thread.Sleep(1000); // 書き込みオブジェクトの破棄 mmva.Dispose(); // 共有メモリの破棄 mmf.Dispose(); } } } |
・Receiver.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO.MemoryMappedFiles; namespace Receiver { class Receiver { // 本当はMutex等で排他制御するべき static void Main(string[] args) { // 作成済み共有メモリ("HelloWorld")のオブジェクト生成 MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("HelloWorld"); // 読み込みオブジェクトの生成 MemoryMappedViewAccessor mmva = mmf.CreateViewAccessor(); // 読み込みオブジェクトの現在のバイト位置 int i = 0; // 読み込み // オブジェクトの数が書き込まれていたらその分データを読む int size = 0; string str = ""; while (!str.Equals("!")) { size = mmva.ReadInt32(i); if (size != 0) { char[] data = new char[size]; i = i + sizeof(int); mmva.ReadArray<char>(i, data, 0, data.Length); str = new string(data); Console.WriteLine(str); i = i + sizeof(char) * data.Length; } System.Threading.Thread.Sleep(100); } // 読み込みオブジェクトの破棄 mmva.Dispose(); // コンソール表示を維持 Console.ReadKey(); } } } |
【実行バッチ】
・test.bat
1 2 3 4 5 6 7 |
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Sender.cs C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Receiver.cs start Sender.exe timeout 1 start Receiver.exe pause exit |
【実行結果】
・Receiverのコンソール
1 2 3 |
Hello World ! |
いかがでしたでしょうか。
今回は、共有メモリを用いたデータの受け渡しを紹介してみました。
記事ではjavaに書き方が近く理解しやすいC#で書きましたが、本当に性能が求められる場合はCやC++で書く場合が多いです。
しかし、その場合もロジックは同じなので、参考になるのではないかと思います。
コメント