Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/283.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何轻松使此计数器属性线程安全?_C#_Thread Safety - Fatal编程技术网

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
    的值
  • 加1
  • 将结果存储在
    DoneCounter
  • 您必须防止在这些步骤之间发生上下文切换的可能性。在getter或setter中锁定不会有帮助,因为该锁的作用域完全存在于其中一个步骤(1或3)中。如果您想确保这三个步骤同时发生而不被中断,那么您的同步必须覆盖这三个步骤。这意味着它必须发生在一个包含这三个元素的上下文中。这可能会成为不属于包含
    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)的人,此代码的用户将需要修改它,以利用调用