web-dev-qa-db-ja.com

C#でnull値をロックできないのはなぜですか?

C#では、null値のロックは許可されていません。ロックする前に値がnullかどうかをチェックできると思いますが、ロックしていないため、別のスレッドがやって来て値をnullにする可能性があります。この競合状態を回避するにはどうすればよいですか?

41
Casebash

Nullになることのない値をロックします。

Object _lockOnMe = new Object();
Object _iMightBeNull;
public void DoSomeKungFu() {
    if (_iMightBeNull == null) {
        lock (_lockOnMe) {
            if (_iMightBeNull == null) {
                _iMightBeNull = ...  whatever ...;
            }
        }
    }
}

また、ダブルチェックロックでこの興味深い競合状態を回避するように注意してください。 ダブルチェックロックでのメモリモデル保証

30
Chris Shain

CLRにはSyncBlockをアタッチする場所がないため、null値でロックすることはできません。これにより、CLRはMonitor.Enter/Exitを介して任意のオブジェクトへのアクセスを同期できます(これはlockが内部で使用するものです)

57
Ana Betts

ここには2つの問題があります。

まず、nullオブジェクトをロックしないでください。 2つのオブジェクト(どちらもnull)をどのように区別できるのか意味がありません。

次に、マルチスレッド環境で変数を安全に初期化するには、ダブルチェックロックパターンを使用します。

if (o == null) {
    lock (lockObj) {
        if (o == null) {
            o = new Object();
        }
    }
}

これにより、別のスレッドがまだオブジェクトを初期化しておらず、シングルトンパターンを実装するために使用できます。

6
Gian Paolo

C#でnull値をロックできないのはなぜですか?

Paulの回答 は、これまでのところ技術的に正しい唯一の問題なので、私はそれを受け入れます。これは、.Netのモニターがすべての参照タイプに添付されている同期ブロックを使用するためです。 nullの変数がある場合、それはオブジェクトを参照していないため、モニターは使用可能な同期ブロックにアクセスできません。

この競合状態を回避するにはどうすればよいですか?

従来のアプローチでは、決してnullにならないことがわかっているオブジェクト参照をロックします。これが保証できない状況に陥った場合、私はこれを非伝統的なアプローチと分類します。 null可能なロックターゲットにつながる可能性のある特定のシナリオを詳細に説明しない限り、ここで説明できることはこれ以上ありません。

2
Brian Gideon

質問の最初の部分はすでに回答されていますが、質問の2番目の部分に何かを追加したいと思います。

特にこの状況では、別のオブジェクトを使用してロックを実行する方が簡単です。これにより問題も解決され、クリティカルセクション内の複数の共有オブジェクトの状態が維持されます。従業員のリストと従業員の写真のリスト。

さらに、この手法は、intやdecimalなどのプリミティブ型のロックを取得する必要がある場合にも役立ちます。

私の意見では、他の誰もが示唆するようにこの手法を使用している場合は、2回nullチェックを実行する必要はありません。例えば受け入れられた回答では、Crisは、ロックされたオブジェクトが実際に変更されているものとは異なるため実際には何の違いもない条件2倍を使用しました。 。

次のコードをお勧めします。

object readonly syncRootEmployee = new object();

List<Employee> employeeList = null;
List<EmployeePhoto> employeePhotoList = null;

public void AddEmployee(Employee employee, List<EmployeePhoto> photos)
{
    lock (syncRootEmployee)
    {
        if (employeeList == null)
        {
            employeeList = new List<Employee>();
        }

        if (employeePhotoList == null)
        {
            employeePhotoList = new List<EmployeePhoto>();
        }

        employeeList.Add(employee);
        foreach(EmployeePhoto ep in photos)
        {
            employeePhotoList.Add(ep);
        }
    }
}

他の誰かが競合状態を見ている場合は、ここに競合状態が表示されません。コメントで返信してください。上記のコードを見るとわかるように、3つの問題を一度に解決します。1つはロック前にnullチェックが不要で、2つ目は2つの共有ソースをロックせずにクリティカルセクションを作成し、3つ目は複数のオブジェクトをロックすると書き込み中の注意不足によりデッドロックを引き起こします。コード。

以下は、プリミティブ型のロックの使用方法です。

object readonly syncRootIteration = new object();

long iterationCount = 0;
long iterationTimeMs = 0;

public void IncrementIterationCount(long timeTook)
{
    lock (syncRootIteration)
    {
        iterationCount++;
        iterationTimeMs = timeTook;
    }
}

public long GetIterationAvgTimeMs()
{
    long result = 0;

    //if read without lock the result might not be accurate
    lock (syncRootIteration)
    {
        if (this.iterationCount > 0)
        {
            result = this.iterationTimeMs / this.iterationCount;
        }
    }

    return result;
}

ハッピースレッド:)

1
Mubashar