C#是否需要在getter和setter中进行锁定

本文关键字:setter 锁定 getter 是否 | 更新日期: 2023-09-27 18:19:25

当多个线程将通过get/set函数访问一个属性/字段时,是否有必要锁定getter和setter。

例如:

您有一个计时器,它定期获取对象的值。以及定期更新此值的过程。

public class Task : IProgressProvider
{
    ...
    public Progress GetProgress()
    {
        // should i lock here?
        return _progress;
    }
    public SetProgress(Progress progress)
    {
        // and lock here?
        _progress = progress;
    }
    public void Execute()
    {
        // ...
        SetProgress(new Progress(10));
        // ...
        SetProgress(new Progress(50));
        // ...
        SetProgress(new Progress(100));
    }
}
public class ProgressReporter
{
    public ProgressReporter()
    {
        _timer = new Timer();
        _timer.Elapsed += Elapsed;
        // ...
    }
    // ...
    public void Elapsed(...)
    {
        var progress = _task.GetProgress();
        // ...
    }
}

问题再次出现:

  • 我应该锁定GetProgress和SetProgress函数吗
  • 如果是,为什么
  • 在这种情况下是否需要易失性(假设有一个或多个处理器内核)
  • 为了保持"简单",我们假设Progress中的属性/字段是只读的编辑:我知道+=等不是原子操作,需要正确的处理(例如锁定)

我认为设置和读取变量_progress是一个原子操作。GetProgress获得当前值并不重要。如果这次没有,它将在GetProgress的下一次调用中获得它。

C#是否需要在getter和setter中进行锁定

如果_progress是引用类型,那么读取和写入其值(即更改引用)实际上是一个原子操作,因此您不需要为示例中的特定代码锁定。但是,如果您在setter或getter中更改了多个字段,或者该字段不是具有原子读/写(例如double)的类型,则需要锁定。

如果您希望多个线程在任何时刻都观察到相同的值,那么实际上您将需要额外的锁定。如果你不在乎一个线程是否可以读取一个值,而另一个线程可以读取另一个值(因为它在两者之间发生了变化),那么锁定似乎真的没有必要。

我肯定会让它变得不稳定。volatile正是为了这个目的。它至少可以防止优化编译器在不应该缓存值的时候缓存值(以防它会做这样的事情)。

总结一下:对于您所需的用法,您应该使_progress易失性,但不需要锁定对它的访问。

根据方法签名,似乎只有一个线程将更新状态。如果是,则不需要对SetProgress进行任何锁定。

如果您确实有多个线程更新Progress变量,那么您仍然不需要在set函数中有锁,因为它是原子函数(假设它是一个引用变量,看起来像这样)。

但是,如果您读取值,然后添加一个数字(例如,取当前进度并将其增加10),则需要锁定该调用。如果这是你的意图,因为你的调用对象不应该真正负责这个对象的完整性,我建议创建一个处理更新的方法。

    public Progress IncrProgress(int incr)
    {
        lock (_progressLock)
        {
            // Get the current progress
            int current = _progress.GetPercentage();
            current += incr;
            _progress = new Progress(current);
        }
        return _progress;
    }

至于您的另一个问题,_progress应该标记为volatile,因为它是由另一个线程更新的。

http://msdn.microsoft.com/en-us/library/x13ttww7(v=vs.71).aspx

如果没有人要更改对象的状态,我认为属性/字段是只读的并不重要。

因此,如果您只有一个线程设置进度,其他线程读取进度,则不需要锁定。

即使是这种情况,让Progress对象不可变也是一个很好的设计——因为你在设计中明确指出:为什么你不需要任何锁定?因为对象无法更改。

此外,如果以后有更改/重新设计(决定更改/变异进度对象),就不会意外引入线程错误。

甚至volatile关键字可能也没有必要?因为你不在乎你是否失去了一两个进度。关键字是关于对字段的"序列化访问"。