C# 如何轻松使此计数器属性线程安全?
我在类中有属性定义,其中只有计数器,这必须是线程安全的,这不是因为C# 如何轻松使此计数器属性线程安全?,c#,thread-safety,C#,Thread Safety,我在类中有属性定义,其中只有计数器,这必须是线程安全的,这不是因为get和set不在同一个锁中,如何做到这一点 private int _DoneCounter; public int DoneCounter { get { return _DoneCounter; } set { lock (sync) {
get
和set
不在同一个锁中,如何做到这一点
private int _DoneCounter;
public int DoneCounter
{
get
{
return _DoneCounter;
}
set
{
lock (sync)
{
_DoneCounter = value;
}
}
}
您可以将_DoneCounter变量声明为“volatile”,以使其线程安全。见此:
如果您希望以保证不受竞争条件约束的方式实现属性,则无法在属性的实现中实现。该操作不是原子操作,它实际上有三个不同的步骤:
DoneCounter
的值DoneCounter
中DoneCounter
属性的任何类的代码
使用对象的人有责任确保线程安全。通常,没有任何具有读/写字段或属性的类可以以这种方式被设置为“线程安全”的。但是,如果您可以更改类的接口,这样就不需要设置器,那么就可以使它更线程安全。例如,如果您知道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); }
你到底想用这些柜台做什么?锁实际上与整数属性没有多大关系,因为整数的读取和写入是原子的,无论有没有锁。锁的唯一好处是增加了记忆障碍;在读取或写入共享变量之前和之后使用
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
获取锁
把你的钱记下来
释放锁
向读取的值添加一个
获取锁
将_DoneCounter设置为计算值
释放锁
这不是很有帮助,因为值可能在get和set之间更改。所需要的是一种能够执行get、计算和set而不需要任何干预操作的方法。有三种方法可以实现这一点:
Threading.Interlocked.Increment
向_计数器添加值
Threading.Interlocked.compareeexchange
循环更新计数器
使用这些方法中的任何一种,都可以基于旧值计算_计数器的新值,以确保写入的值基于写入时_计数器的值。使用类提供原子操作,即本质上是线程安全的,如本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); }
}
}
如果您不仅希望某些线程偶尔会同时写入计数器,而且希望许多线程会继续这样做,那么您希望有多个计数器,至少有一条缓存线彼此相隔,并让不同的线程写入不同的计数器,在需要计数时求和 这会使大多数线程彼此隔离,从而阻止它们从内核中刷新彼此的值,并使彼此变慢。(除非您能保证每个线程保持分离,否则您仍然需要联锁)
对于绝大多数情况,您只需要确保偶尔的争用不会弄乱值,在这种情况下,Sean U的答案在各方面都更好(这样的条带计数器对于无争用的使用速度较慢)。相关:U可以使用联锁增量。这应该很容易。@Svisstack,如果你真的询问“counter”类型的属性(参见Sean U的回答),最好将标题中的“this”更新为“counter”。在什么情况下这不是线程安全的?你想实现什么?哈哈哈,那页上的例子太没用了,去微软吧。这可能是实现他所寻找的最简单的方法。+1,semms要工作,我将属性重写为公共变量,但我不能接受你,因为问题是关于属性的,我认为如果volaite varaible在类(private)内,并且将与公共属性一起使用,这将不起作用。这应该起作用:c类{private volatile int\u n;public nValue{get{返回这个。{u n;}set{this。{u n=value;}}}这将不起作用。
volatile
不会阻止线程交叉执行读写操作。它只会阻止线程使用处理器缓存或寄存器。它适用于允许线程读取或写入某个值,但决不能同时读取或写入这两个值的情况。在其他任何地方,它唯一可靠的作用就是使程序不慢。联锁和锁带来了它们自己的记忆障碍,这意味着很多(但不是全部)在向它们写入时使用这些值的情况下,您不需要它。我以类似计数器+=X的方式使用该计数器;并且X不是1;-)我有一个计数器,从中减去不等于1的值,然后您可以使用它。此代码确实保证调用IncrementDoneCounter()
tweep将始终精确地增加计数器两次,这可能是OP想要的。但是,对于那些进入此代码希望保留不同计数值(例如,对于唯一ID)的人,此代码的用户将需要修改它,以利用调用