CSharp

概要

 排他制御とは、一度に1つのスレッドしかアクセスできない制限をかけることを指します。排他制御を行わないと、マルチスレッドなプログラムでは、データの不整合を引き起こす場合があります。
 デッドロックとは複数のスレッドが、リソースをロックしあい、結果としてプログラムが停止してしまうことです。以下のプログラムはかなり高い確率でデッドロックを発生します。
 複数のリソースをロックしなくてはならないようであれば、ロック取得の順番は常に一定にしておくことが効果的です。

C# の lock ステートメントはクリティカルセクションです。

class Clazz{
	private static object syncobj = new object();

	public void Func1(){
		lock(syncobj){
			// コード1
		}
	}

	public void Func2(){
		lock(syncobj){
			// コード2
		}
	}
}

lock は上記のようにやれば、「コード1」を実行中に「コード2」が実行される事はありません、という使い方です。当然ですけど、まったく同じオブジェクトをロックしなければなりません。

#ちなみにこの方法は結構重いので、使いどころはよく考慮する。

なぜLockするか?何をLockする?

スレッドの最も効率的な方法は非同期ですね、つまり、各スレッドは、実行されて使用する時には相互依存、待つがないです。しかし、複数のスレッドがあるリソースをアクセスする時、同期メカニズムが必要、このリソースの扱いは、同じ時間で一つのスレッドしか操作できないように制御する必要があります。すなわち、その操作の原子性を保証するようにします。Lockは、最もよく使用される同期方法、 フォーマット:

Lock(objectA)
{
  //処理A
}

意味は:

  • objectAはそれをロックしましたか?ロックされていなかったら、私がロックしますよ、逆にロックされている場合、objectAがリリースされるまで待ちます。
  • Lockしたら、「処理A」を処理しているところで、ほかのスレッドが「処理A」を実行することも、objectAを利用することもできません。
  • 「処理A」の処理が完了すると、objectAをリリースし、「処理A」とobjectAはほかのスレッドにアクセスできようになります。

lock(this)がいいか?

ソースコードの例を見ましょう

using System;
using System.Threading;
namespace Namespace1
{
   class C1
   {
       private bool deadlocked = true;

       //このメソッドはLockを利用している、Lockされている処理は同時にひとうのスレッドのみアクセスできるようにしたいです。
       public void LockMe(object o)
       {
           lock (this)
           {
               while(deadlocked)
               {
                   deadlocked = (bool)o;
                   Console.WriteLine("Foo: I am locked :(");
                   Thread.Sleep(500);
               }
           }
       }

       //すべてのスレッドで同時にアクセスできるメソッド
       public void DoNotLockMe()
       {
           Console.WriteLine("I am not locked :)");
       }
   }

   class Program
   {
       static void Main(string[] args)
       {
           C1 c1 = new C1();

           //t1スレッド内にLockMeを呼び出して、deadlockをtrueにすると、デッドロックが発生します。
           Thread t1 = new Thread(c1.LockMe);
           t1.Start(true);
           Thread.Sleep(100);

           //メインスレッドlock c1
           lock (c1)
           {
               //Lockしないメソッドを呼び出す
               c1.DoNotLockMe();
               //Lockするメソッドを呼び出す、デッドロックを解除してみる
               c1.LockMe(false);
           }
       }
   }
t1スレッド内に、LockeMeがLock(this)、つまりはMainメソッド中のC1、この場合t1スレッド中のLock処理が実行完了まで、メインスレッド中にLock(C1)を呼び出しても、C1をアクセスできません、つまりすべてC1関連の処理が完成できません。だからC1.DoNotLockMe()は実行されていません。~

C1のコードを修正します。

   class C1
   {
       private bool deadlocked = true;
       private object locker = new object();

       //このメソッドはLockを利用している、Lockされている処理は同時にひとうのスレッドのみアクセスできるようにしたいです。
       public void LockMe(object o)
       {
           lock (locker)
           {
               while(deadlocked)
               {
                   deadlocked = (bool)o;
                   Console.WriteLine("Foo: I am locked :(");
                   Thread.Sleep(500);
               }
           }
       }

       //すべてのスレッドで同時にアクセスできるメソッド
       public void DoNotLockMe()
       {
           Console.WriteLine("I am not locked :)");
       }
   }

 今回はあるオブジェクト(locker)をロック対象にする、LockMe中にすべての対象をロックするではなく、Lockerオブジェクトだけをロックしています。 実行してみたら、t1がデッドロック現象がまた出ましたが、DoNotLockMe()はメインスレッドからアクセスできます、LockMe()はアクセスできません。(理由はロックされているlockerオブジェクトはリリースされていませんので)

ポイント:

  1. Lock(this)の欠点は一つのスレッド(本例のt1)、あるメソッド中にlock(this)を実行すると、this(クラス)をロックされ、ほかのすべてのスレッドがクラスのインスタンスをアクセスできなくなります。ほかのスレッド(例のメインスレッド)中に、クラスを利用時、もlock(c1)を使用していますから。
  2. ロックするのはロック内の処理だけではなく、ロック自身もスレッドセーフ
  3. ほかの操作への影響がないprivateなオブジェクトをロックする対象にする。


本当にほしかったのはこういうブログだったんだ

コメント:



(画像の文字列を入力して下さい)

トップ   編集 凍結解除 差分 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019/12/02 (月) 12:45:43 (1628d)

yVoC[UNLIMITȂ1~] ECirŃ|C Yahoo yV LINEf[^[Ōz500~`I


z[y[W ̃NWbgJ[h COiq 萔O~ył񂫁z COsیI COze