.Netスレッド同期のベストプラクティス

同期は、複数のスレッドが共有リソースに同時にアクセスするのを防ぐために使用される概念です。これを使用して、複数のスレッドがオブジェクトのプロパティまたはメソッドを同時に呼び出すのを防ぐことができます。共有リソースにアクセスするコードのブロックを同期するか、オブジェクトのプロパティとメンバーへの呼び出しを同期して、任意の時点で1つのスレッドのみがクリティカルセクションに入ることができるようにするだけです。

この記事では、.Netの同期とスレッドセーフに関連する概念と、関連するベストプラクティスについて説明します。

専用ロック

排他的ロックは、任意の時点で、1つのスレッドだけがクリティカルセクションに入ることができるようにするために使用されます。アプリケーションに排他ロックを実装するには、次のいずれかを使用する必要があります。

  • ロック-これは、Monitorクラスの静的メソッドの構文ショートカットであり、共有リソースの排他ロックを取得するために使用されます
  • Mutex-複数のプロセス間で機能できることを除いて、lockキーワードに似ています
  • SpinLock-スレッドコンテキストスイッチのオーバーヘッドを回避することにより、共有リソースの排他ロックを取得するために使用されます

Monitorクラスの静的メソッドまたはlockキーワードを使用して、アプリケーションにスレッドセーフを実装できます。Monitorクラスの静的メンバーとlockキーワードの両方を使用して、共有リソースへの同時アクセスを防ぐことができます。lockキーワードは、同期を実装するためのショートカット方法にすぎません。ただし、マルチスレッドアプリケーションで複雑な操作を実行する必要がある場合は、MonitorクラスのWait()メソッドとPulse()メソッドが役立ちます。

次のコードスニペットは、Monitorクラスを使用して同期を実装する方法を示しています。

private static readonly object lockObj = new object();

        static void Main(string[] args)

        {

            Monitor.Enter(lockObj);

                       try

            {

               //Some code

            }

                  finally

            {

                Monitor.Exit(lockObj);

            }

        }

lockキーワードを使用した同等のコードは、次のようになります。

    private static readonly object lockObj = new object();

        static void Main(string[] args)

        {  

            try

            {

                lock(lockObj)

                {

                    //Some code

                }             

            }

            finally

            {

                //You can release any resources here

            }

        }

Mutexクラスを利用して、プロセス間で実行できる同期を実装できます。lockステートメントと同様に、Mutexによって取得されたロックは、ロックの取得に使用されたのと同じスレッドからのみ解放できることに注意してください。Mutexを使用してロックを取得および解放することは、lockステートメントを使用して同じことを行うよりも比較的時間がかかります。

SpinLockの背後にある主なアイデアは、スレッド間のコンテキスト切り替えに伴うコストを最小限に抑えることです。スレッドが共有リソースのロックを取得できるまでしばらく待機またはスピンできる場合、スレッド間のコンテキスト切り替えに伴うオーバーヘッドを回避できます。 。クリティカルセクションが最小限の作業を実行する場合、SpinLockの候補として適しています。

非排他的ロック

非排他的ロックを利用して、並行性を制限できます。非排他的ロックを実装するには、次のいずれかを使用できます。

  • セマフォ-共有リソースに同時にアクセスできるスレッドの数を制限するために使用されます。本質的には、特定の共有リソースのコンシューマーの数を同時に制限するために使用されます。
  • SemaphoreSlim-非排他的ロックを実装するためのSemaphoreクラスの高速で軽量な代替手段。
  • ReaderWriterLockSlim-ReaderWriterLockSlimクラスは、ReaderWriterLockクラスの代わりとして.Net Framework3.5で導入されました。

ReaderWriterLockSlimクラスを使用して、頻繁な読み取りが必要であるが更新頻度が低い共有リソースの非排他的ロックを取得できます。したがって、頻繁な読み取りとまれな更新を必要とする共有リソースの相互に排他的なロックの代わりに、このクラスを使用して、共有リソースの読み取りロックと排他的な書き込みロックを取得できます。

デッドロック

デッドロックが発生する可能性があるため、型にlockステートメントを使用したり、lock(this)などのステートメントを使用してアプリケーションに同期を実装したりしないでください。共有リソースで取得したロックを長期間保持している場合にも、デッドロックが発生する可能性があることに注意してください。ロックステートメントで不変の型を使用しないでください。例として、lockステートメントのキーとして文字列オブジェクトを使用することは避けてください。パブリックタイプでlockステートメントを使用することは避けてください。インターンされていないプライベートオブジェクトまたは保護されたオブジェクトをロックすることをお勧めします。本質的に、デッドロック状態は、複数のスレッドが共有リソースのロックを解放するために互いに待機しているときに発生します。デッドロックの詳細については、このMSDNの記事を参照してください。