如何轻松使这个计数器属性线程安全?

Svi*_*ack 11 c# thread-safety

我在类中有属性定义,我只有计数器,这必须是线程安全的,这不是因为get并且set不在同一个锁中,如何做到这一点?

    private int _DoneCounter;
    public int DoneCounter
    {
        get
        {
            return _DoneCounter;
        }
        set
        {
            lock (sync)
            {
                _DoneCounter = value;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Sea*_*n U 23

如果您希望以DoneCounter = DoneCounter + 1保证不受竞争条件限制的方式实施该属性,则无法在该属性的实现中完成.该操作不是原子操作,实际上有三个不同的步骤:

  1. 检索值DoneCounter.
  2. 加1
  3. 将结果存储在DoneCounter.

您必须防止在任何这些步骤之间发生上下文切换的可能性.锁定getter或setter内部将无济于事,因为该锁的范围完全存在于其中一个步骤(1或3)中.如果要确保所有三个步骤一起发生而不被中断,那么您的同步必须涵盖所有三个步骤.这意味着它必须在包含所有这三个的上下文中发生.这可能最终会成为不属于任何类包含DoneCounter属性的代码.

使用您的物体的人负责照顾线程安全.通常,没有具有读/写字段或属性的类可以以这种方式"线程安全".但是,如果您可以更改类的接口以便不需要setter,则可以使其更加线程安全.例如,如果你知道DoneCounter只增加和减少,那么你可以重新实现它,如下所示:

private int _doneCounter;
public int DoneCounter { get { return _doneCounter; } }
public int IncrementDoneCounter() { return Interlocked.Increment(ref _doneCounter); }
public int DecrementDoneCounter() { return Interlocked.Decrement(ref _doneCounter); }
Run Code Online (Sandbox Code Playgroud)

  • 然后你可以使用[Interlocked.Add()](http://msdn.microsoft.com/en-us/library/33821kfh.aspx). (4认同)
  • 这段代码确保两次调用`IncrementDoneCounter()`总是将计数器增加两次,这可能是OP想要的.但是,对于那些想要保留*distinct*count值的代码(例如,对于唯一ID),此代码的用户需要修改它以使用对Interlocked.Increment的调用的返回值. `. (2认同)
  • 好点,@布莱恩.我已经相应地编辑了这个例子. (2认同)

Dav*_*rke 5

使用Interlocked类提供原子操作,即本质上是线程安全的,如以下 LinqPad 示例所示:

void Main()
{
    var counters = new Counters();
    counters.DoneCounter += 34;
    var val = counters.DoneCounter;
    val.Dump(); // 34
}

public class Counters
{
    int doneCounter = 0;
    public int DoneCounter
    {
        get { return Interlocked.CompareExchange(ref doneCounter, 0, 0); }
        set { Interlocked.Exchange(ref doneCounter, value); }
    }
}
Run Code Online (Sandbox Code Playgroud)