こんにちは!
マクロの記録から一歩踏み出し、「本物のアプリケーション開発」を目指すあなたの姿、とても素晴らしいですね。
今回は、VB.NETでの開発において避けて通れない、しかし多くの初学者がハマりがちな「Shared(静的)メンバーの落とし穴と、マルチスレッド環境での安全なクラス設計」についてお話しします。
「 `Shared` って、インスタンスを作らなくていいから便利!」と多用していませんか?
実はそこには、複数動くプログラム(マルチスレッド)の世界において、データがグチャグチャに破壊される恐ろしい罠が潜んでいます。
ここをクリアすれば、あなたのVB.NETのコードは「動くだけのスクリプト」から「堅牢なプロフェッショナルコード」へと劇的に進化します。一緒に本質をマスターしていきましょう!
—
1. Shared(静的)メンバーってなに? 便利さの裏にあるリスク
まずは基本のおさらいです。通常、クラスのメンバー(変数やメソッド)を使うには、以下のように `New` キーワードを使って「インスタンス(実体)」を生成する必要がありますよね。
.net
‘ 通常のクラス
Dim user As New User()
user.Name = “田中”
user.SayHello()
しかし、`Shared` キーワードを付与すると、インスタンスを一つも生成しなくても、クラス名から直接呼び出せるようになります。
.net
‘ Shared(静的)クラスのイメージ
Public Class MathHelper
Public Shared Function Add(a As Integer, b As Integer) As Integer
Return a + b
End Function
End Class
‘ 呼び出し(Newしなくていいので楽ちん!)
Dim result As Integer = MathHelper.Add(5, 10)
「わざわざ `New` しなくていいから、全部 `Shared` にしちゃえばラクじゃん!」
……そう思ったあなた、ちょっと待ってください。ここに大きな落とし穴があります。
Shared変数は「全員で共有するホワイトボード」
`Shared` な変数は、メモリ上にたった一つだけ存在し、プログラム全体(すべてのユーザー、すべてのスレッド)で共有されます。
これは、オフィスの真ん中に置かれた「共有のホワイトボード」のようなものです。
全員が自由に書き込める反面、Aさんが書き込んでいる最中に、Bさんが勝手に内容を書き換えたり消したりしたらどうなるでしょうか?
これが、マルチスレッド環境で発生する「競合状態(レースコンディション)」の正体です。
—
2. 【図解】マルチスレッドで何が起きるのか?
現代のパソコンやサーバーは、同時に複数の処理(スレッド)を並行して実行しています。
例えば、Webアプリやバックグラウンド処理で、2つのスレッドが同時に `Shared` 変数を操作したとしましょう。
[スレッドA] ──► Shared変数「Counter」の値を読み取る (現在値: 10)
[スレッドB] ──► Shared変数「Counter」の値を読み取る (現在値: 10)
[スレッドA] ──► 10 に 1 を足して書き込む ──► 【Counter = 11】
[スレッドB] ──► 10 に 1 を足して書き込む ──► 【Counter = 11】 (あれっ!? 2増えるはずが1しか増えてない!)
このように、データの整合性が失われ、意図しないバグ(しかも再現が非常に難しい厄介なバグ)を引き起こします。
—
3. 実践! `SyncLock` でスレッドセーフなコードを書く
では、複数のスレッドから同時にアクセスされても安全(スレッドセーフ)にするにはどうすればよいのでしょうか?
答えは簡単です。「誰かがホワイトボードを使っているときは、鍵をかけて他の人を入れないようにする」のです。
VB.NETでは、この排他制御を `SyncLock` ステートメントを使って非常に簡単に実装できます。
危険なコード(スレッドセーフではない例)
.net
Public Class UnsafeCounter
‘ 共有変数
Public Shared Count As Integer = 0
Public Shared Sub Increment()
‘ 複数のスレッドが同時にここを通ると値が壊れます!
Count += 1
End Sub
End Class
安全なコード(`SyncLock` による排他制御)
.net
Public Class SafeCounter
Private Shared _count As Integer = 0
‘ ロック専用のオブジェクトを用意する(これが「部屋の鍵」になります)
Private Shared ReadOnly _lockObject As New Object()
Public Shared Sub Increment()
‘ SyncLockブロックの中には、同時に1つのスレッドしか入れません
SyncLock _lockObject
_count += 1
End SyncLock
End Sub
Public Shared Function GetCount() As Integer
SyncLock _lockObject
Return _count
End SyncLock
End Function
End Class
コードの解説
1. `Private Shared ReadOnly _lockObject As New Object()`
ロック専用のオブジェクトを用意します。中身は何でもいいのですが、外部から勝手にロックされないよう `Private` にするのが鉄則です。
2. `SyncLock _lockObject` 〜 `End SyncLock`
このブロックで囲まれた処理は、あるスレッドが実行している間、他のスレッドは扉の外で待たされることになります。これにより、同時に変数が書き換えられるのを完全に防ぐことができます。
—
4. 現場で役立つ設計の判断基準
「じゃあ、すべてのメソッドや変数を `SyncLock` で囲めば完璧だな!」と思ったあなた、待ってください。
排他制御には「処理の速度が落ちる(ロック待ちが発生するため)」というデメリットがあります。そのため、以下の原則を頭に入れておきましょう。
- 状態を持たない処理(メソッドのみ)なら `Shared` は安全
先ほどの `MathHelper.Add(a, b)` のように、内部に変数を保持せず、渡された引数だけで計算して結果を返すメソッドは、複数スレッドから同時に呼ばれてもデータが壊れることはありません。安心して `Shared` にしてください。
- 値を保持する変数(状態を持つクラス)は、できるだけインスタンス化する
「クラスのインスタンスを生成して、それぞれのオブジェクトにデータを保持させる(非Sharedにする)」設計のほうが、そもそもスレッド間の干渉を防ぎやすく、オブジェクト指向としても自然で安全な場合がほとんどです。
—
まとめ:ここをクリアすれば基本はバッチリ!
今回は、VB.NETにおける `Shared` メンバーの危険性と、マルチスレッド環境での備えについて解説しました。
- `Shared` 変数はプログラム全体で1つしか存在しない「共有のホワイトボード」。
- 複数スレッドから同時にアクセスされるとデータが破壊される(競合状態)。
- 同時アクセスを防ぎたいときは `SyncLock` を使って排他制御を行う。
- 本当にその変数が `Shared` であるべきか、設計を立ち止まって考える。
ここを意識できるようになれば、単にコードが動くだけでなく、「大規模なシステムでも壊れない頑丈なコード」が書けるエンジニアへ確実に近づいています。
VB.NETの世界は奥が深いですが、一つひとつの仕組みをこうして紐解いていけば絶対に怖くありません。
一緒に一歩ずつ、確かな技術を身につけていきましょう!
