【入門編】実務中級者向け:VB.NETにおけるShared(静的)メンバーの落とし穴:マルチスレッド環境での競合を防ぐスレッドセーフなクラス設計 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

こんにちは!開発現場で日々、コードと格闘お疲れ様です。
マクロの記録や、なんとなく動くコードを書く段階から一歩進み、「実務で通用する堅牢なシステムを作りたい!」そう思っているあなたへ。

今回は、VB.NETの中級者への登竜門であり、一歩間違えるとシステムを崩壊させる魔物――`Shared`(静的)メンバーとマルチスレッドの競合問題について、徹底的に紐解いていきます。

「ここをクリアすれば、VB.NETのオブジェクト指向とメモリの挙動はバッチリですよ!」
優しく、そしてプロの現場で通用する本質的な知識を伝授しますので、ぜひ最後までついてきてくださいね。

1. `Shared`って便利だけど…実は危ない?

VB.NETを書いていると、インスタンス(実体)を生成せずに `ClassName.MethodName()` のように呼び出せる `Shared` キーワードは非常に便利ですよね。

‘ インスタンス化しなくても呼べる便利なShared
Public Class AppConfig
Public Shared Property TimeoutSeconds As Integer = 30
End Class

「どこからでもアクセスできて最高じゃん!」と思って多用していませんか?
実はこの `Shared`、プログラムが動いている間、メモリ上にたった一つだけ存在し続け、アプリケーションの終了まで消えません。

もし、この「共有された変数」に、複数の処理(スレッド)が同時にアクセスしてきたらどうなるでしょうか?

マルチスレッドの「すれ違い事故」

現代のPCやサーバーは、同時に複数の処理(マルチスレッド)を行うのが当たり前です。
例えば、Webアプリやバックグラウンド処理で、2つのスレッドが同時に `Shared` 変数を書き換えようとしたとします。

  • スレッドA:値に「10」を書き込もうとする
  • スレッドB:値に「20」を書き込もうとする

結果、意図しない値(中途半端に混ざったデータ)が読み書きされ、「データ破損(競合状態:Race Condition)」や、最悪の場合はアプリケーションがクラッシュする原因になります。

2. 実践:競合が起きる「危険なコード」を見てみよう

百聞は一見に如かず。まずは、マルチスレッド環境下で `Shared` 変数がバグを引き起こす典型的なコードを見てみましょう。

Imports System.Threading

Public Class DangerousCounter
‘ 共有されるカウンター
Public Shared Count As Integer = 0

‘ カウントアップするメソッド
Public Shared Sub Increment()
‘ 以下の「1増やす」という処理は、実は内部で複数のステップに分かれています
‘ 1. 現在の値を読み取る
‘ 2. 1を加算する
‘ 3. 変数に書き戻す
Count += 1
End Sub
End Class

この `DangerousCounter.Increment()` を、10個のスレッドから同時に10万回呼び出してみたとします。
数学的に考えれば、最終的な `Count` は 「1,000,000」 になるはずですよね。

しかし、マルチスレッドでこれを実行すると、90万台の数字になったり、バラバラの結果になります。 なぜなら、スレッドAが「読み取って書き戻す」隙間に、スレッドBが割り込んで古い値を上書きしてしまうからです。これが競合状態の正体です。

3. 解決策:スレッドセーフなクラス設計の極意

この「同時に触らせない」ようにする制御を、専門用語で排他制御(Mutual Exclusion)と呼びます。

VB.NETでは、`SyncLock` ステートメントを使うことで、安全なスレッドセーフなコードを簡単に書くことができます。

`SyncLock` を使った安全な実装

Imports System.Threading

Public Class SafeCounter
‘ 共有されるカウンター
Private Shared _count As Integer = 0

‘ 排他制御用のロックオブジェクト(外部から触れないようPrivate ReadOnlyにするのが鉄則!)
Private Shared ReadOnly LockObject As New Object()

‘ スレッドセーフなプロパティ
Public Shared ReadOnly Property Count As Integer
Get
SyncLock LockObject
Return _count
End SyncLock
End Get
End Property

‘ スレッドセーフなインクリメントメソッド
Public Shared Sub Increment()
‘ SyncLockで囲まれたブロックには、同時に1つのスレッドしか入れません
SyncLock LockObject
_count += 1
End SyncLock
End Sub
End Class

コードのポイント解説

1. ロック専用のオブジェクトを用意する
`Private Shared ReadOnly LockObject As New Object()` を用意します。文字列や数字ではなく、ただの「鍵(Objectのインスタンス)」を作るのがポイントです。
2. `SyncLock` で処理を保護する
`SyncLock LockObject` から `End SyncLock`までの間は、「VIPルーム」のようなものです。あるスレッドが中にいる間は、他のスレッドは外で待たされます。これにより、データの書き込み競合が完全に防止されます。

4. 知っておくべき「デッドロック」という落とし穴

排他制御は強力ですが、一歩間違えるとデッドロック(Deadlock)という恐ろしい罠にハマります。

  • スレッドA は「鍵1」を持っていて、「鍵2」を待っている
  • スレッドB は「鍵2」を持っていて、「鍵1」を待っている

この状態になると、お互いが永遠に相手を待ち続け、プログラムが完全にフリーズ(硬直)します。

デッドロックを防ぐための鉄則

  • ロックの順番を常に一定にする(複数の鍵を使う場合は、必ず「鍵1→鍵2」の順で取得する)。
  • 必要以上に広い範囲を `SyncLock` で囲まない(処理時間が長くなると、それだけ他のスレッドが待たされ、パフォーマンスが低下します)。
  • なるべく `Shared` な可変状態(Mutable State)を作らない設計を目指す。これが最高級のベストプラクティスです。

5. まとめ:プロのエンジニアへの第一歩

今回は、`Shared` メンバーにおけるマルチスレッドの競合と、`SyncLock` による排他制御について解説しました。

  • `Shared` 変数は便利だが、マルチスレッド環境では競合(バグ)のリスクがある。
  • 同時に書き換えてほしくないクリティカルな処理は、`SyncLock` で保護する。
  • ロック用の専用オブジェクト(`New Object()`)を用意して安全に管理する。

「動けばいいや」のコードから、「並行処理の負荷に耐える堅牢なコード」へ。
この `SyncLock` の概念をマスターすれば、あなたの書くVB.NETコードの信頼性はプロのレベルへとグッと引き上げられます。

現場での設計に、ぜひ役立ててくださいね!それでは、次のステップでも一緒にスキルを磨いていきましょう。

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