5

当多个线程将通过 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 函数。
  • 如果是,为什么?
  • 在这种情况下是否需要 volatile(假设一个或多个处理器内核)
  • 为了保持“简单”,我们假设 Progress 的属性/字段是只读的。编辑:我知道 += 等不是原子操作,需要正确处理(fe 锁定)

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

4

3 回答 3

4

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

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

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

总结一下:对于您所需的用途,您应该使用_progressvolatile,但您不需要锁定对它的访问。

于 2013-06-20T16:51:40.923 回答
1

根据您的方法签名,您似乎只有一个线程将更新状态。如果是这样,您不需要对 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

于 2013-06-20T15:18:07.627 回答
0

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

因此,如果您只有一个线程设置进度而其他人正在阅读它 - 不需要锁定。

即使是这种情况,使 Progress 对象不可变也是一个不错的设计 - 因为您在设计中明确说明:为什么不需要任何锁定?因为对象不能改变。

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

甚至可能不需要volatile 关键字?因为您不在乎是否丢失一两个进度标记。关键字是关于对字段的“序列化访问”。

于 2013-06-20T15:24:56.907 回答