スレッドセーフ コンポーネント
スレッド間でのリソース共有は、マルチスレッド プログラミングでは頻繁に必要になります。 たとえば、複数のスレッドが共有データベースにアクセスしたり、システム変数を更新したりする場合があります。 同時に複数のスレッドが競争して共有リソースへアクセスしようとするので、"競合状態" が発生する可能性があります。 競合状態は、あるスレッドによってリソースが無効な状態に変更された後、別のスレッドがそのリソースにアクセスし、無効な状態で利用しようとするときに発生します。 次に例を示します。
Public Class WidgetManipulator
Public TotalWidgets as Integer = 0
Public Sub AddWidget()
TotalWidgets += 1
Console.WriteLine("Total widgets = " & TotalWidgets.ToString)
End Sub
Public Sub RemoveWidgets()
TotalWidgets -= 10
End Sub
End Class
public class WidgetManipulator
{
public int TotalWidgets = 0;
public void AddWidget()
{
TotalWidgets++;
Console.WriteLine("Total widgets = " + TotalWidgets.ToString());
}
public void RemoveWidgets()
{
TotalWidgets -= 10;
}
}
このクラスでは 2 つのメソッドが公開されています。 1 つ目のメソッド (AddWidget) では、TotalWidgets フィールドに 1 を追加し、この値をコンソールに書き込んでいます。 2 つ目のメソッドでは、TotalWidgets の値から 10 を減算しています。 2 つのスレッドが同時に WidgetManipulator クラスの同じインスタンスにアクセスしようとしたときに、何が起こるかを考えてみます。 一方のスレッドが RemoveWidgets を呼び出すと同時に、もう一方のスレッドが AddWidget を呼び出す可能性があります。 このとき、TotalWidgets の値は、最初のスレッドが正確な値を報告する前に、もう 1 つのスレッドによって変更される可能性があります。 この競合状態が原因で、不正確な結果が報告されたり、データの破損が生じたりすることが考えられます。
ロックを使用した競合状態の回避
"ロック" を使用すると、コードのクリティカル セクションを競合状態から保護できます。 ロックは Visual Basic では SyncLock Statement というキーワードで、C# では lock Statement というキーワードで表されます。ロックを使用することにより、オブジェクトの排他的な実行権限をシングル スレッドの実行に与えることができます。 ロックの使用例を次に示します。
SyncLock MyObject
' Insert code that affects MyObject.
End SyncLock
lock(MyObject)
{
// Insert code that affects MyObject.
}
ロックに行き当たると、指定されたオブジェクト (前の例では MyObject) の実行は、このスレッドがオブジェクトに排他アクセスできるようになるまでブロックされます。 ロックの末尾に到達すると、ロックが解放され実行が通常どおり続行されます。 ロックを取得できるのは、参照を返すオブジェクトに対してだけです。 値型をこの方法でロックすることはできません。
ロックの短所
ロックを使用することにより、複数のスレッドが 1 つのオブジェクトに対して同時にアクセスしないことが保証されますが、ロックによってパフォーマンスが著しく低下する場合があります。 多数の異なるスレッドが実行中のプログラムがあるとします。 特定のオブジェクトを使用するために各スレッドが実行前にオブジェクトの排他ロックを取得するまで待機する必要がある場合、これらのスレッドはすべて実行を中断し、それぞれが順番待ちの状態になってしまうのでパフォーマンスが低下します。 以上の理由から、ロックを使用するのは、1 つの単位として実行するコードが存在する場合に限定する必要があります。 たとえば、互いに独立した複数のリソースを更新することができます。 このようなコードは "アトミック" と呼ばれます。 アトミック実行する必要のあるコードだけにロックを限定することにより、良好なパフォーマンスを維持しながらデータの安全性も保証されるマルチスレッド コンポーネントを記述できます。
さらに、デッドロックが発生するような状況を回避するように注意することも必要です。 この場合、複数のスレッドは、互いが共有リソースを解放するのを待ちます。 たとえば、Thread 1 がリソース A のロックを保持し、リソース B の解放を待っている場合に、 Thread 2 がリソース B のロックを保持し、リソース A の解放を待っているとします。 この場合は、いずれのスレッド処理も続行できません。 デッドロックを回避する唯一の方法は、慎重なプログラミングです。
参照
処理手順
チュートリアル : Visual Basic による簡単なマルチスレッド コンポーネントの作成
チュートリアル : Visual C# による簡単なマルチスレッド コンポーネントの作成