8

问题是我一直在使用lock 语句来保护我的代码的关键部分,但现在,我意识到我可以允许该关键代码的并发执行,前提是满足某些条件。
有没有办法调节锁?

4

8 回答 8

11

我认为这个问题叫“种族条件!”。如果在检查后不久条件从真变为假,但在线程进入代码的关键部分之前怎么办?或者当一个线程正在执行它的过程中?

于 2008-10-28T12:59:34.183 回答
8
bool locked = false;
if (condition) {
    Monitor.Enter(lockObject);
    locked = true;
}
try {
    // possibly critical section
}
finally {
    if (locked) Monitor.Exit(lockObject);
}

编辑:是的,除非您可以确保在线程进入时条件是恒定的,否则存在竞争条件。

于 2008-10-28T13:02:01.383 回答
8

我不是线程专家,但听起来您可能正在寻找类似的东西(双重检查锁定)。这个想法是在获取锁之前和之后检查条件。

private static object lockHolder = new object();

if (ActionIsValid()) {
  lock(lockHolder) {
    if (ActionIsValid()) {
       DoSomething();    
    }
  }
}
于 2008-10-28T15:29:03.443 回答
7
Action doThatThing = someMethod;

if (condition)
{
  lock(thatThing)
  {
     doThatThing();
  }
}
else
{
  doThatThing();
}
于 2008-10-28T12:57:11.723 回答
6

实际上,为了避免竞争条件,我很想在ReaderWriterLockSlim这里使用 - 将并发访问视为读锁,将独占访问视为写锁。这样,如果条件发生变化,您将不会在该区域中仍然盲目执行一些不适当的代码(在错误假设它是安全的情况下);有点冗长,但是(格式化为空格):

        if (someCondition) {
            lockObj.EnterReadLock();
            try { Foo(); }
            finally { lockObj.ExitReadLock(); }
        } else {
            lockObj.EnterWriteLock();
            try { Foo(); }
            finally { lockObj.ExitWriteLock(); }
        }
于 2008-10-28T13:01:08.610 回答
2

如果您有许多需要条件锁定的方法/属性,您不想一遍又一遍地重复相同的模式。我提出以下技巧:

非重复条件锁模式

使用私有助手struct实现IDisposable,我们可以封装条件/锁而没有可测量的开销。

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

现在您可以让调用者决定是否需要锁定(代价高昂)。

于 2020-08-19T16:15:54.070 回答
1

我猜你有一些看起来有点像这样的代码:

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 );
}

应该工作,不是吗?

于 2008-10-28T12:58:56.913 回答
1

如上所述,使用双重检查锁定模式。这就是 IMO 的诀窍 :)

确保您将锁定对象设置为static,如 not.that.dave.foley.myopenid.com 的示例中所列。

于 2008-11-10T05:52:25.673 回答