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的下一次调用中获得它。
如果_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关键字可能也没有必要?因为你不在乎你是否失去了一两个进度。关键字是关于对字段的"序列化访问"。