原始并发读写的线程安全性

时间:2012-01-07 23:51:39

标签: c# .net multithreading concurrency thread-safety

  • 下面简化说明,.NET如何处理这种情况?
  • 如果它会导致问题,我是否必须锁定/门禁访问可能有时被写入+从不同线程访问的每个字段/属性?

某个地方

public class CrossRoads(){
    public int _timeouts;
}

后台线程编写者

public void TimeIsUp(CrossRoads crossRoads){
    crossRoads._timeouts++;
}

可能在同一时间,试图阅读其他地方

public void HowManyTimeOuts(CrossRoads crossRoads){
    int timeOuts = crossRoads._timeouts;
}

5 个答案:

答案 0 :(得分:12)

简单的答案是,如果从多个线程同时访问上述代码,则能够导致问题。

.Net框架提供了两种解决方案:互锁和线程同步。

对于简单的数据类型操作(即整数),使用Interlocked class的联锁将正常工作,是推荐的方法。

事实上,interlocked提供了特定的方法(递增和递减),使这个过程变得简单:

将IncrementCount方法添加到CrossRoads类:

public void IncrementCount() {
    Interlocked.Increment(ref _timeouts);
}

然后从后台工作人员那里打电话:

public void TimeIsUp(CrossRoads crossRoads){
    crossRoads.IncrementCount();
}

除非32位操作系统上的64位值,否则读取值是原子的。有关详细信息,请参阅Interlocked.Read method documentation

对于类对象或更复杂的操作,您需要使用线程synchronization locking(在VB中锁定C#或SyncLock)。

这是通过在要应用锁定的级别(例如,在类中)创建静态同步对象,获取对该对象的锁定,以及在该锁定内执行(仅)必要的操作来实现的: / p>

    private static object SynchronizationObject = new Object();

    public void PerformSomeCriticalWork()
    {
        lock (SynchronizationObject)
        {
            // do some critical work
        }
    }

答案 1 :(得分:4)

好消息是对int的读写保证是原子的,所以没有撕裂的值。但是,不能保证安全++,并且读取可能会缓存在寄存器中。还有指令重新排序的问题。

我会用:

Interlocked.Increment(ref crossroads._timeouts);

对于写入,这将确保没有值丢失,并且;

int timeouts = Interlocked.CompareExchange(ref crossroads._timeouts, 0, 0);

对于读取,因为这遵循与增量相同的规则。严格来说,“易变”可能足以让人阅读,但人们对它的了解却很少,以至于Interlocked似乎(IMO)更安全。无论哪种方式,我们都在避免锁定。

答案 2 :(得分:3)

好吧,我不是C#开发人员,但这就是它通常在这个级别上工作的方式:

  

.NET如何处理这种情况?

解锁。不太可能保证是原子的。

  

我是否必须锁定/门控访问可能有时被写入+从不同线程访问的每个字段/属性?

是。另一种方法是对客户端可用的对象进行锁定,然后告诉客户端在使用实例时必须锁定对象。这将减少锁定采集的数量,并为您的客户保证更加一致,可预测的状态。

答案 3 :(得分:3)

忘记dotnet。在机器语言级别,crossRoads._timeouts++将实现为INC [memory]指令。这称为读 - 修改 - 写指令。这些指令对于单个处理器上的多线程*是原子的(基本上是通过时间切片实现的),但对于使用多个处理器或多个内核的多线程而言,它们不是原子的。

所以:

如果您可以保证只有TimeIsUp()会修改crossRoads._timeouts,并且如果您可以保证只有一个线程会执行TimeIsUp(),那么这样做是安全的。 TimeIsUp()中的写作可以正常工作,HowManyTimeOuts()(以及其他任何地方)的阅读都可以正常工作。但是如果你也在其他地方修改crossRoads._timeouts,或者如果你再生成一个后台线程编写器,你将遇到麻烦。

在任何一种情况下,我的建议都是安全地将其锁定。

(*)它们对于单个处理器上的多线程是原子的,因为线程之间的上下文切换发生在周期性中断上,而在x86架构上,这些指令对于中断是原子的,这意味着如果发生中断当CPU正在执行这样的指令时,中断将一直等到指令完成。对于更复杂的指令,例如具有REP前缀的指令,这不适用。

答案 4 :(得分:2)

虽然int可能是CPU的“原生”大小(一次处理32位或64位),但如果从不同的线程读取和写入同一个变量,最好是锁定这个变量和同步访问。

从来没有保证int进行读/写可能是原子的。

您也可以在此处使用Interlocked.Increment