簡體   English   中英

如何在 C# 中編寫條件鎖?

[英]How can I write a conditional lock in C#?

問題是我一直在使用lock 語句來保護我的代碼的關鍵部分,但是現在,我意識到如果滿足某些條件,我可以允許該關鍵代碼的並發執行。
有沒有辦法調節鎖?

我認為這個問題叫“競爭條件!”。 如果條件在檢查后不久、但在線程進入代碼的臨界區之前從真變為假怎么辦? 或者當一個線程正在執行它的過程中?

我不是線程專家,但聽起來您可能正在尋找這樣的東西(雙重檢查鎖定)。 這個想法是在獲取鎖之前和之后檢查條件。

private static object lockHolder = new object();

if (ActionIsValid()) {
  lock(lockHolder) {
    if (ActionIsValid()) {
       DoSomething();    
    }
  }
}
Action doThatThing = someMethod;

if (condition)
{
  lock(thatThing)
  {
     doThatThing();
  }
}
else
{
  doThatThing();
}
bool locked = false;
if (condition) {
    Monitor.Enter(lockObject);
    locked = true;
}
try {
    // possibly critical section
}
finally {
    if (locked) Monitor.Exit(lockObject);
}

編輯:是的,除非您可以確保在線程進入時條件保持不變,否則存在競爭條件。

實際上,為了避免競爭條件,我很想在這里使用ReaderWriterLockSlim - 將並發訪問視為讀鎖,將獨占訪問視為寫鎖。 這樣,如果條件發生變化,您最終不會在該區域中盲目執行一些不適當的代碼(假設它是安全的); 有點冗長,但是(格式化為空格):

        if (someCondition) {
            lockObj.EnterReadLock();
            try { Foo(); }
            finally { lockObj.ExitReadLock(); }
        } else {
            lockObj.EnterWriteLock();
            try { Foo(); }
            finally { lockObj.ExitWriteLock(); }
        }

如上所述,使用雙重檢查鎖定模式 這就是海事組織的訣竅:)

確保您的鎖對象為static ,如 not.that.dave.foley.myopenid.com 的示例中所列。

如果您有許多需要條件鎖定的方法/屬性,您不想一遍又一遍地重復相同的模式。 我提出以下技巧:

非重復條件鎖模式

使用實現IDisposable的私有幫助器struct我們可以封裝條件/鎖而無需可測量的開銷。

public void DoStuff()
{
    using (ConditionalLock())
    {
        // Thread-safe code
    }
}

這很容易實現。 這是演示此模式的示例類:

public class Counter
{
    private static readonly int MAX_COUNT = 100;

    private readonly bool synchronized;
    private int count;
    private readonly object lockObject = new object();

    private int lockCount;

    public Counter(bool synchronized)
    {
        this.synchronized = synchronized;
    }

    public int Count
    {
        get
        {
            using (ConditionalLock())
            {
                return count;
            }
        }
    }

    public int LockCount
    {
        get
        {
            using (ConditionalLock())
            {
                return lockCount;
            }
        }
    }

    public void Increase()
    {
        using (ConditionalLock())
        {
            if (count < MAX_COUNT)
            {
                Thread.Sleep(10);
                ++count;
            }
        }
    }

    private LockHelper ConditionalLock() => new LockHelper(this);

    // This is where the magic happens!
    private readonly struct LockHelper : IDisposable
    {
        private readonly Counter counter;
        private readonly bool lockTaken;

        public LockHelper(Counter counter)
        {
            this.counter = counter;

            lockTaken = false;
            if (counter.synchronized)
            {
                Monitor.Enter(counter.lockObject, ref lockTaken);
                counter.lockCount++;
            }
        }

        private void Exit()
        {
            if (lockTaken)
            {
                Monitor.Exit(counter.lockObject);
            }
        }

        void IDisposable.Dispose() => Exit();
    }
}

現在,讓我們創建一個小示例程序來演示其正確性。

class Program
{
    static void Main(string[] args)
    {
        var onlyOnThisThread = new Counter(synchronized: false);
        IncreaseToMax(c1);

        var onManyThreads = new Counter(synchronized: true);
        var t1 = Task.Factory.StartNew(() => IncreaseToMax(c2));
        var t2 = Task.Factory.StartNew(() => IncreaseToMax(c2));
        var t3 = Task.Factory.StartNew(() => IncreaseToMax(c2));
        Task.WaitAll(t1, t2, t3);

        Console.WriteLine($"Counter(false) => Count = {c1.Count}, LockCount = {c1.LockCount}");
        Console.WriteLine($"Counter(true)  => Count = {c2.Count}, LockCount = {c2.LockCount}");
    }

    private static void IncreaseToMax(Counter counter)
    {
        for (int i = 0; i < 1000; i++)
        {
            counter.Increase();
        }
    }
}

輸出:

Counter(false) => Count = 100, LockCount = 0
Counter(true)  => Count = 100, LockCount = 3002

現在您可以讓調用者決定是否需要鎖定(代價高昂)。

我猜你有一些看起來像這樣的代碼:

private Monkey GetScaryMonkey(int numberOfHeads){
    Monkey ape = null;        
    lock(this) {
        ape = new Monkey();
        ape.AddHeads(numberOfHeads);            
    }
    return ape;
}

為了使這個有條件,你不能這樣做:

private Monkey GetScaryMonkey(int numberOfHeads){
    if ( numberOfHeads > 1 ) {
         lock(this) {
            return CreateNewMonkey( numberOfHeads );          
        }
    }
    return CreateNewMonkey( numberOfHeads );
}

應該工作,不是嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM