第4回 デッドロックの回避とスレッド間での同期制御 ― マルチスレッド・プログラミングにおける排他制御と同期制御(後編) ―連載.NETマルチスレッド・プログラミング入門(3/3 ページ)

» 2005年06月15日 00時00分 公開
前のページへ 1|2|3       

マルチスレッドに関するそのほかの話題

 マルチスレッド・プログラミングの基礎についての話題は以上であるが、よりマルチスレッドを活用するためのいくつかの話題を簡単に紹介しておこう。

■Mutex(ミューテックス)による排他制御

 Mutexクラス(System.Threading名前空間)はlockステートメントと同じ排他制御を行うための手段である(「mutex」とは「相互排除」の意味)。Mutexオブジェクトを作成し、WaitOneメソッドでロックを取得し、ReleaseMutexメソッドでロックを解放することで排他制御を行う。

 lockステートメントとの違いは、Mutexクラスではスレッド間だけではなくプロセス間での排他制御が行えることである。これによって、アプリケーションをまたがった排他制御を簡単に実現することができ、アプリケーションの多重起動のチェックなどによく使用される。次のList4は単純なMutexクラスによる排他制御のサンプルである。

using System;
using System.Threading;

public class List4
{
  // Mutexオブジェクトの作成

  public static Mutex mutex = new Mutex();

  public static void Main()
  {
    for (int i = 0; i < 20; i++)
    {
      (new Thread(new ThreadStart(Transaction))).Start();
    }
  }

  private static void Transaction()
  {
    try
    {
      // Mutexロック取得
      mutex.WaitOne();
      Console.WriteLine("トランザクション開始");
      Thread.Sleep(100);
      Console.WriteLine("トランザクション終了");
    }
    catch
    {
    }
    finally
    {
      // Mutexロック解放
      mutex.ReleaseMutex();
    }
  }
}

List4 Mutexクラスにより排他制御を行うC#のサンプル・プログラム
List4.csのダウンロード

Imports System
Imports System.Threading

Public Class List4
  ' Mutexオブジェクトの作成
  Public Shared mutex As New Mutex()

  Public Shared Sub Main()
    Dim i As Integer
    For i = 0 To 19
      Dim thread As Thread = New Thread(New ThreadStart(AddressOf Transaction))
      thread.Start()
    Next i
  End Sub 'Main

  Private Shared Sub Transaction()
    Try
      ' Mutexロック取得
      mutex.WaitOne()
      Console.WriteLine("トランザクション開始")
      Thread.Sleep(100)
      Console.WriteLine("トランザクション終了")
    Catch
    Finally
      ' Mutexロック解放
      mutex.ReleaseMutex()
    End Try
  End Sub 'Transaction
End Class 'List4

List4 Mutexクラスにより排他制御を行うVB.NETのサンプル・プログラム
List4.vbのダウンロード

 Mutexクラスを利用したサンプル・プログラムは「.NET TIPS:Windowsアプリケーションの多重起動を禁止するには?」でも紹介されているので参照していただきたい。

■Monitorクラス以外の同期制御の方法

 今回では同期制御の手段としてMonitorクラスについて解説したが、これ以外にもイベントを用いる方法(AutoResetEventクラスManualResetEventクラスを利用する方法)もSystem.Threading名前空間には用意されている。

■スレッドごとのデータ領域となるデータスロット

 ThreadクラスにはAllocateDataSlotAllocateNamedDataSlotという静的メソッドが用意されている。これらを利用することで、「データスロット」と呼ばれるスレッドごとにデータを格納しておく領域を確保することができる。スレッドごとに個別に保持したいデータなどがある場合に利用する価値があるだろう。

 確保したデータスロットに対してはSetDataメソッドでデータの設定を、GetDataメソッドでデータの取得を行う。不要となったデータスロットはFreeNamedDataSlotメソッドで解放しておく必要がある。

■現在のスレッドを取得する

 Threadクラスの静的メソッドであるCurrentThreadメソッドを使用することで、現在のコードがどのスレッドで実行されているのかを取得することができる。

Thread thread = Thread.CurrentThread;

■スレッドセーフなコレクション・クラス

 ArrayListクラスやHashtableクラスなど.NET Frameworkのコレクション・オブジェクト(System.Collections名前空間のクラス)を複数のスレッドからアクセスするときには、「SyncRoot」というプロパティを用いてコレクションをスレッドセーフにすることができる。このプロパティは、データの先頭から順番にコレクションの中身の一覧を表示するときに、途中で要素の挿入などがないようにするためになどに使用される。

 具体的なサンプル・コードなどは「.NET TIPS:スレッドセーフなコレクション・オブジェクトを作成するには?」を参照していただきたい。

■lockステートメントでロック対象とするオブジェクト

 いくつかのサンプル・コードでは、よく「this」(VB.NETの場合には「Me」)や、typeof演算子(VB.NETではGetType演算子)を用いたロックの取得が頻繁に使用されているが、実際にはそういったコードはロック排他制御によるパフォーマンスの低下を引き起こしやすいプログラムになる。なぜなら、クラスのインスタンスやクラスのタイプ(Typeオブジェクト)のロックは、そのクラスのコード以外の場所でもできてしまうからである(無関係な処理の間で排他制御を行ってしまうことになる)。

 もしクラスの外部で思いもかけずに長時間ロックを保持してしまうような処理があれば、その影響はクラスの内部にまで及んでしまう。このような状況を避けるために、スレッドセーフなクラスでロックを利用するときには、ロック専用のオブジェクトを作成し排他制御を行うとよい。

public class Resource
{
  public static void Method()
  {
    lock (typeof(Resource))
    {
      // 何かの処理
    }
  }
}

パフォーマンスの低下を引き起こす可能性が大きいコード
typeof演算子により得られるResourceクラスのTypeオブジェクトはほかのクラスなどからも取得可能であり、無用な排他制御が行われてしまう可能性がある。

public class Resource
{
  private readonly object lockObject = new object();

  public static void Method()
  {
    lock (lockObject)
    {
      // 何かの処理
    }
  }
}

ロック専用のオブジェクトを用いたより安全なコード
このようにしておけば、ロック対象となるオブジェクトが利用される範囲をクラス内に限定できる。

 この記述方法は、本連載のサンプル・プログラムでもすでに何度か利用している。

マルチスレッド・プログラミングにおける注意点のまとめ

 以上、全4回にわたり解説してきたが、最後にマルチスレッド・プログラミングを行うに当たっての留意点をまとめておこう。

  • マルチスレッド化を検討するときは、パフォーマンスの向上が得られるかどうかを考える
  • マルチスレッド・プログラミングでは、発見しにくいバグや考慮しなくてはならない点が多い
  • マルチスレッド・プログラミングの実装方法を、Threadクラス、ThreadPoolクラス、デリゲート、タイマーの中から適切に選択すること
  • スレッドでの処理が長時間にわたる可能性がある場合には、スレッドプールの使用は避けること
  • 別スレッドでの処理を1つのクラス内で完結させることができるのであれば、外部からはマルチスレッドを意識させない構造にしておく(ただしドキュメントにはきちんと記述すること)
  • 共通リソースとなるクラスは別クラスにし、スレッドセーフなコンポーネントとして排他制御などを行い、データをしっかりと守ること。サブクラス化もできないようにしておく
  • 可能な限りロックの取得は行わなくてもよい設計にすること
  • ロックをしている時間と範囲はできる限り短くするように努力すること
  • InterlockクラスやReaderWriterLockクラスを使用することができないか検討すること
  • 複数リソースのロックを同時に取得するようなコードはデッドロックの原因になるので避けること
  • スレッドプールによる処理の内部で、さらにスレッドプールを使用する処理を行わないこと
  • ロックはできる限りprivateなオブジェクトに対してかけること

あとがき

 マルチスレッド・プログラミングの要求はこれからますます増していくと考えられる。特にCPUがデュアルコア化してくると、普通のPCが複数のCPUを持つことに等しく、マルチスレッド化によってパフォーマンスの向上が期待できる場面はますます多くなってくるだろう。

 しかし、マルチスレッド・プログラミングにはシングルスレッド・プログラミングにはなかった注意すべき点が多い。本連載がマルチスレッド・プログラムを開発する指針になれば幸いである。

「連載.NETマルチスレッド・プログラミング入門」のインデックス

連載.NETマルチスレッド・プログラミング入門

前のページへ 1|2|3       

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。