交错锁 - 我什么时候使用它?

8

我刚刚了解到Interlocked类,现在有一些基本问题。

据我的理解,在多线程操作数值变量时应该使用Interlocked。如果这个说法是正确的,那么进行读取或者普通使用变量怎么办?

例如:

if ((iCount % 100) == 0)

我需要在那里使用Interlocked语句吗?

当我初始化变量时呢:

Int32 iCount = 0;

在实施前,我需要确保我理解这个。


可能重复:https://dev59.com/aWHVa4cB1Zd3GeqPjAwp和https://dev59.com/VHVC5IYBdhLWcg3w4Vb6 - valverij
我看到了那篇文章,但它跳过了我的基础问题。 - webdad3
3个回答

8

这里有许多因素,但主要与不稳定性原子性有关。在您的陈述中:

if ((iCount % 100) == 0)

Do I need to use an Interlocked statement there?

首先我们需要问“什么是 iCount?”。如果它是 long / ulong 类型,则不能保证它是原子的,因此你绝对需要一些保护措施(例如通过 Interlocked)来避免获取到“损坏”的值(在更新过程中读取它的一半,给出一个它实际上从未拥有过的幻影值 - 例如,从 00000000 更改为 FFFFFFFF,你可以读取 0000FFFF 或 FFFF0000)。如果它是 int 类型,则保证是原子的。下一个问题是:我需要看到更新吗?CPU 内置了各种级别的缓存,看似从字段中读取的代码可能实际上只是从本地寄存器或缓存中读取 - 并且从未触及实际内存。如果存在风险,则可以通过使用 Interlocked 来减轻这种情况,尽管在许多情况下,使用 volatile 也可以防止这种情况。
第三个问题更棘手,涉及到更新:你是否想要“最后编辑的盲目胜出”?如果是这样,只需更新该值(可能使用 volatile 以允许读取)- 然而 - 如果两个线程正在编辑,则存在丢失更新的风险。例如,如果两个线程同时增加和减少,最终值可能是 0 或 1 - 不一定是你想要的。Interlocked 提供了进行带有更改检测的更新的方法,并提供了执行常见操作(如增量 / 减量 / 添加等)的辅助方法。
关于你的另一个问题:

What about when I'm initializing the variable:

Int32 iCount = 0;

字段初始化器仅在一个线程上执行,因此不需要额外的保护 - 这很好。


然而!多线程编程很困难。如果您有任何疑虑,请保持简单:使用 lock。例如(假设您想要每个实例同步):

private int iCount = 0;
private readonly object syncLock = new object();
...
lock(syncLock) {
    // code that reads or manipulates iCount
}

在许多情况下,这很好用。

在我的例子中,iCount 是一个 Int32。 - webdad3
@JeffV 是的,但在我看来,你需要明白这很重要,这也是为什么我在答案中包含它的原因。如果我只是说“是的,那是原子性”,而没有提供上下文,那么你可能会在实际代码中将其更改为long,这是非常危险的。同样,原子性只是这里故事的一部分 - Marc Gravell
谢谢您的澄清。我现在正在研究锁(syncLock)的构造。感谢您的答案。 - webdad3
我之所以问是因为有一段代码使用InsertOnSubmit命令读取和操作iCount。我注意到在这个过程中加锁会显著增加时间。 - webdad3
@Jeff,如果没有更完整的情景概述,我无法发表评论,只能说“哦”(或许还会疑惑地挑起眉毛)。 - Marc Gravell
显示剩余2条评论

2
当对共享的可变状态进行多线程处理时,您需要进行同步。您不需要使用Interlocked。 Interlocked是为高级用户设计的。我建议您使用C#语句lock,并仅在简单情况(增加共享计数器)或性能关键情况下使用Interlocked。
Interlocked只能用于一次访问单个变量,并且仅支持相当原始的操作。使用Interlocked同步多个变量会非常困难。
在您的示例中,没有人能够确定您是否需要进行同步,因为线程安全是整个程序的属性,而不是单个语句或函数的属性。您需要将所有操作共享状态的代码视为一个整体。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接