当多个线程通过get / set-functions访问属性/字段时,是否有必要锁定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();
// ...
}
}
问题再次出现:
我认为变量_progress的设置和读取是原子操作。 GetProgress获取当前值非常重要。如果不是这次,它将在下次调用GetProgress时获得它。
答案 0 :(得分:3)
如果_progress
是引用类型,那么读取和写入其值(即更改引用)确实是一个原子操作,因此您不需要锁定示例中的特定代码。但是如果您在setter或getter中更改了多个字段,或者该字段不是具有原子读/写的类型(例如double),那么您需要锁定。
如果您希望多个线程在任何时刻观察到相同的值,那么您确实需要额外的锁定。如果你不在乎一个线程是否可以读取一个值而另一个线程是另一个值(因为它在中间发生了变化),那么锁定似乎并不是必需的。
我肯定会让它变得不稳定。 volatile
正是出于这个目的。它至少会阻止优化编译器在不应该的情况下缓存该值(如果它会做这样的事情)。
总结一下:对于您的必需用法,您应该使_progress
易变,但您不需要锁定对它的访问权。
答案 1 :(得分: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应该被标记为易失性,因为它被另一个线程更新。
http://msdn.microsoft.com/en-us/library/x13ttww7(v=vs.71).aspx
答案 2 :(得分:0)
如果没有人会改变对象的状态,我认为属性/字段不重要。
因此,如果您只有一个线程设置进度而其他线程正在读取它 - 则不需要锁定。
即使是这种情况,使Progress对象不可变也是一个不错的设计 - 因为你在设计中明确说明:为什么你不需要任何锁定?因为对象无法改变。
此外,如果稍后进行更改/重新设计(决定更改/改变进度对象),则不会意外地引入线程错误。
即使是易变的关键字可能也没有必要?既然你不在乎你是否输了一两个进度。关键字全部是关于“序列化访问”字段。