【実務・中級編】上級プロフェッショナル向け:VB.NETにおける「MemoryMappedFile」を活用したプロセス間通信:複数アプリ間で大容量データを数マイクロ秒で超高速共有するメカニズム – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

上級プロフェッショナル向け:VB.NETにおける「MemoryMappedFile」を活用したプロセス間通信:複数アプリ間で大容量データを数マイクロ秒で超高速共有するメカニズム

業務システムにおいて、複数のアプリケーション(プロセス)間でデータを連携させる要件は日常茶飯事だ。
例えば、常駐型のデータ配信用インプロセス・ワーカーと、ユーザー操作を受け付けるUIフロントエンドの分離。あるいは、レガシーなVB6ランタイムとモダンな.NET Coreプロセスの共存。

ここで多くの開発者が直面するのが、「プロセス間通信(IPC)のオーバーヘッド」という壁だ。
TCP/IPソケット、名前付きパイプ、あるいはファイルやデータベースを介したデータ授受。これらは構造化された安全な手法である一方、OSのカーネル空間とユーザー空間のコピー、シリアライズ/デシリアライズ、ディスクI/Oといった「重労働」を伴う。数メガバイトのデータをやり取りするだけで、ミリ秒単位の遅延が発生し、リアルタイム性が求められる現場では致命傷となる。

「ディスクやソケットを介さず、OSの物理メモリをダイレクトに共有し、数マイクロ秒で数ギガバイトのデータを同期できたら——」

このアーキテクチャ上の要求に対する唯一にして最強の回答が、.NETの `System.IO.MemoryMappedFiles` である。
今回は、VB.NETの極限のパフォーマンスを引き出し、実務の現場で即座に使える堅牢なプロセス間通信(IPC)の設計と実装を伝授しよう。

1. なぜ「ファイルやDB」ではダメなのか? IPCのパラダイムシフト

従来の業務システムでよく使われるファイルI/Oやデータベース(SQLiteやLocalDB等)を介したデータ共有は、プロセス間通信としては「最も贅沢で遅いアプローチ」だ。

  • ファイル共有: データの書き込みと読み込みの都度、OSのキャッシュマネージャーとストレージ(SSD/HDD)へのアクセスが発生する。ファイルロックの競合制御(Mutex/Semaphore)のオーバーヘッドも無視できない。
  • データベース: コネクション確立、SQLパース、トランザクションログの書き込み、インデックス更新など、データ実体にたどり着くまでのレイヤーが厚すぎる。

対して、メモリマップトファイル(MMF)は、OSの仮想メモリマネージャー(VMM)に直接働きかける。
OSのページファイル(またはディスク上のファイル)の特定の領域を、複数のプロセスの仮想アドレス空間に「マッピング(直結)」する。これにより、プロセスAが自身のメモリ上の特定のポインタ(アドレス)に書き込んだデータは、シリアライズすら経由せず、メモリバスの速度そのままでプロセスBの仮想アドレス空間に即座に反映される。

[プロセス A] –(書き込み)–> [共有メモリ空間 (OS管理)] <--(読み込み)-- [プロセス B] (シリアライズ不要、メモリコピーのみ、数マイクロ秒のレイテンシ) このメカニズムを使いこなせば、数万件の構造化レコードであっても、まるで同一プロセス内の配列を参照するが如く、遅延ゼロで共有できるのだ。 ---

2. 堅牢な設計:バグと競合を防ぐアーキテクチャの鉄則

メモリ共有は「速さ」と引き換えに、「安全性の担保をすべて開発者自身が握る」というリスクを孕む。
複数のプロセスが同一のメモリ領域へ無秩序にアクセスすれば、当然ながら「ダーティリード(不整合な読み込み)」や「レースコンディション(競合)」が発生し、アプリのクラッシュやデータ破損に直結する。

プロダクション環境で破綻しないための3大鉄則を明記する。

1. 排他制御(Mutex)の徹底:
書き込みと読み込みが同時に発生しないよう、OS名前付きの `Mutex`(相互排他ロック)を必ずラップする。
2. 構造体のメモリレイアウトの固定(StructLayout):
VB.NETのマネージドオブジェクトをそのままメモリに書き込んではならない。ガベージコレクタ(GC)に移動させられないよう、構造体に `StructLayout(LayoutKind.Sequential)` を指定し、アンマネージドメモリと同等の正確なバイトレイアウトを強制する。
3. ライフサイクル管理(OwnerとConsumerの分離):
「誰がメモリ領域を生成(Create)し、誰が破棄(Dispose)するのか」の責任を明確にする。基本的には、マスタープロセスが領域を生成し、ワーカープロセスはオープン(OpenExisting)して利用する形に徹するべきだ。

3. 実装:コピペで動く超高速IPCプロダクションコード

ここからは、実際にVB.NETで動作する堅牢なMemoryMappedFileラッパーと、書き込み・読み込みを行う実装コードを提示する。
今回は、パフォーマンス測定用カウンターと数万件のセンサーデータを想定した高密度構造体を共有するシナリオとする。

共有データ構造体の定義

Imports System.Runtime.InteropServices

‘ メモリ上に直接配置するため、レイアウトを厳密に固定する

Public Structure SharedPayload
Public MessageId As Long

Public StatusText As String
Public Value As Double
End Structure

※実際には文字列の固定長確保に注意が必要だが、今回はシンプルかつ高パフォーマンスなレイアウトを前提とする。

プロセス間通信マネージャー(コアロジック)

以下は、メモリマップトファイルの生成・アタッチ、および安全な排他書き込み/読み込みを提供するクラスだ。

Imports System.IO.MemoryMappedFiles
Imports System.Threading

Public NotInheritable Class MemoryMappedIPC
Implements IDisposable

Private Const MutexName As String = “Global\VB_Net_MMF_Mutex_202X”
Private Const MapName As String = “Global\VB_Net_MMF_DataMap_202X”

Private ReadOnly _mapCapacity As Long
Private _mmf As MemoryMappedFile
Private _mutex As Mutex
Private _disposed As Boolean = False

”’

”’ コンストラクタ:データの書き込み元(オーナー)または読み込み先として初期化
”’

Public Sub New(capacityBytes As Long, isCreator As Boolean)
_mapCapacity = capacityBytes

‘ グローバルスコープのMutexでプロセス間排他制御を確立
Dim createdNew As Boolean
_mutex = New Mutex(False, MutexName, createdNew)

If isCreator then
‘ オーナー側:ファイルバックアップなしの純粋なメモリ共有領域を作成
_mmf = MemoryMappedFile.CreateOrOpen(MapName, _mapCapacity, MemoryMappedFileAccess.ReadWrite)
Else
‘ コンシューマー側:既存の領域にアタッチ
_mmf = MemoryMappedFile.OpenExisting(MapName, MemoryMappedFileRights.ReadWrite)
End If
End Sub

”’

”’ データを安全に書き込む(排他制御付き)
”’

Public Sub WriteData(Of T As Structure)(ByRef data As T)
_mutex.WaitOne()
Try
Using accessor = _mmf.CreateViewAccessor(0, Marshal.SizeOf(Of T)())
accessor.Write(0, data)
End Using
Finally
_mutex.ReleaseMutex()
End Try
End Sub

”’

”’ データを安全に読み込む(排他制御付き)
”’

Public Function ReadData(Of T As Structure)() As T
_mutex.WaitOne()
Try
Dim result As T = Nothing
Using accessor = _mmf.CreateViewAccessor(0, Marshal.SizeOf(Of T)(), MemoryMappedFileAccess.Read)
accessor.Read(0, result)
End Using
Return result
Finally
_mutex.ReleaseMutex()
End Try
End Function

#Region “IDisposable Support”
Public Sub Dispose() Implements IDisposable.Dispose
If Not _disposed Then
If _mmf IsNot Nothing Then
_mmf.Dispose()
End If
If _mutex IsNot Nothing Then
_mutex.Dispose()
End If
_disposed = True
End If
End Sub
#End Region
End Class

実行・検証用コード(Mainモジュール)

このプログラムを2つ以上の別ウィンドウ(別プロセス)で同時に起動し、一方を「Writer(書き込み)」、もう一方を「Reader(読み込み)」として動作させることで、その圧倒的な速度と堅牢性を体感できる。

Module Program
Sub Main()
Console.WriteLine(“=== VB.NET MemoryMappedFile IPC デモ ===”)
Console.Write(“モードを選択してください (1: Writer / 2: Reader): “)
Dim mode = Console.ReadLine()

Dim payloadSize = Marshal.SizeOf(GetType(SharedPayload))

If mode = “1” Then
‘ — Writerプロセス —
Using ipc As New MemoryMappedIPC(payloadSize, isCreator:=True)
Console.WriteLine(“Writer起動。データを高速送信します。[Ctrl+C]で終了。”)
Dim id As Long = 0

While True
id += 1
Dim data As New SharedPayload With {
.MessageId = id,
.StatusText = $”Running OK (Count: {id})”,
.Value = DateTime.Now.Ticks Mod 10000 / 100.0
}

Dim sw = Stopwatch.StartNew()
ipc.WriteData(data)
sw.Stop()

Console.WriteLine($”[WRITE] ID:{id} 完了 (所要時間: {sw.ElapsedTicks} ticks / 約 {sw.Elapsed.TotalMicroseconds:F2} μs)”)
Thread.Sleep(100) ‘ 100msごとに更新
End While
End Using

ElseIf mode = “2” Then
‘ — Readerプロセス —
Using ipc As New MemoryMappedIPC(payloadSize, isCreator:=False)
Console.WriteLine(“Reader起動。データをポーリング受信します。[Ctrl+C]で終了。”)

While True
Dim sw = Stopwatch.StartNew()
Dim data = ipc.ReadData(Of SharedPayload)()
sw.Stop()

Console.WriteLine($”[READ] ID:{data.MessageId} | Text:{data.StatusText} | Val:{data.Value:F2} (読込時間: {sw.Elapsed.TotalMicroseconds:F2} μs)”)
Thread.Sleep(100)
End While
End Using
End If
End Sub
End Module

4. 現場で直面するトラブルシューティングとベストプラクティス

この仕組みを実際のエンタープライズ業務システムに組み込む際、熟練エンジニアが押さえておくべき知見を共有する。

  • 構造体のサイズ変更時の注意点:

`SharedPayload` の構造体レイアウト(フィールドの追加・削除)を変更した場合、Writer側とReader側で構造体サイズが不一致を起こすと、メモリアクセス違反(`ArgumentException` や `AccessViolationException`)が発生する。バージョン管理が必要な場合は、ペイロードの先頭にマジックナンバーやバージョンIDを持たせる設計にすること。

  • 例外時のMutex解放 (AbandonedMutexException):

書き込み中にプロセスが強制終了(タスクマネージャーからの強制終了など)された場合、Mutexが解放されずに残り、他のプロセスがデッドロックを起こす可能性がある。本番コードでは `Try…Catch` で `AbandonedMutexException` を捕捉し、適切にリカバリするロジックを組み込むべきだ。

  • x86とx64の混在に注意:

プロセス間で共有する構造体のパディング(アラインメント)は、32bit(x86)と64bit(x64)のプロセス間で異なる場合がある。原則として、プロセス間通信を行うすべてのアプリケーションは、ターゲットプラットフォームを `x64` に統一することを強く推奨する。

総括

Visual Basic (VB.NET) は、しばしば「レガシーな言語」「簡単な処理に向くが高度なことは苦手」という誤ったレッテルを貼られがちだ。しかし、.NET Runtime の下層にあるインフラストラクチャ――今回紹介した `MemoryMappedFiles` やアンマネージド相互運用(P/Invoke / Marshal)を正しく理解し、駆使すれば、C#やC++に全く引けを取らない超高速かつモダンなシステムアーキテクチャを構築できる。

ディスクI/Oのボトルネックに悩む時代は終わった。
数マイクロ秒の世界をあなたの業務アプリに実装し、圧倒的なパフォーマンスの優位性を手に入れてほしい。

タイトルとURLをコピーしました