我在类中有属性定义,我只有计数器,这必须是线程安全的,这不是因为get
和set
不在同一个锁中,怎么做?< / p>
private int _DoneCounter;
public int DoneCounter
{
get
{
return _DoneCounter;
}
set
{
lock (sync)
{
_DoneCounter = value;
}
}
}
答案 0 :(得分:23)
如果您希望以DoneCounter = DoneCounter + 1
保证不受竞争条件限制的方式实施该属性,则无法在该属性的实现中完成。该操作不是原子操作,实际上是三个不同的步骤:
DoneCounter
。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); }
答案 1 :(得分:4)
使用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); }
}
}
答案 2 :(得分:2)
你究竟想用柜台做什么?锁对整数属性的影响并不大,因为整数的读写都是原子的,有或没有锁定。锁可以获得的唯一好处是增加了内存屏障;在读取或写入共享变量之前和之后使用Threading.Thread.MemoryBarrier()
可以达到相同的效果。
我怀疑你真正的问题是你正试图做一些像“DoneCounter + = 1”这样的事情,即使有锁定,它也会执行以下事件序列:
Acquire lock Get _DoneCounter Release lock Add one to value that was read Acquire lock Set _DoneCounter to computed value Release lock
不是很有用,因为值可能会在get和set之间发生变化。所需要的是一种在没有任何干预操作的情况下执行获取,计算和设置的方法。有三种方法可以实现:
Threading.Interlocked.Increment
向_Counter添加值
Threading.Interlocked.CompareExchange
循环更新_Counter
使用这些方法中的任何一种,都可以根据旧值计算_Counter的新值,以这种方式保证写入的值基于_Counter在写入时的值。 / p>
答案 3 :(得分:2)
如果您不仅期望某些线程偶尔会同时写入计数器,但是许多线程将继续这样做,那么您希望有几个计数器,至少一个缓存线除了彼此,并有不同的线程写入不同的计数器,当你需要计数时将它们相加。
这可以使大多数线程彼此保持不同,从而阻止它们将每个其他值从内核中刷出,并相互减慢速度。 (除非你能保证每个线程保持独立,否则你仍然需要互锁。)
对于绝大多数情况,你只需要确保偶尔的争用不会弄乱数值,在这种情况下,Sean U的答案在各方面都更好(像这样的条纹计数器对于无争议的使用来说速度较慢)。
答案 4 :(得分:0)
您可以将_DoneCounter变量声明为“volatile”,以使其成为线程安全的。见:
http://msdn.microsoft.com/en-us/library/x13ttww7%28v=vs.71%29.aspx