" Threadsafe"的关系与#34;原子操作"

时间:2014-05-04 02:20:47

标签: c# multithreading thread-safety

从这里的MSDN(http://msdn.microsoft.com/en-us/library/aa691278(v=vs.71).aspx)你可以看到基本类型如int,byte ......都是可读/可写的原子。所以我想知道因为它们都是“原子的”:

“原子操作”和“锁定”之间的关系是什么?在我看来,如果操作是“原子的”,我们不再需要锁定,因为它们必须是“线程安全的”,我是对的吗?

无论如何,网站论坛上有人告诉我,他们是对的吗?

1)任何参考类型都是“原子操作”。

2)i ++或--i等类型不是原子操作(那么如何检查哪个操作是“原子的”,哪个不是?)

我总是对这些问题感到困惑....


@DanielBrückner和@keshlam:

似乎混合操作如++ i(首先读/然后写)不是原子的。但是,如果我将它们分成两个步骤,我感到很感兴趣:

//suppose I have "i" defined and assigned a value
void Fun()
{
   if(i==1)
   {
     i=2;
   }
}

如果上面的代码将由多个线程调用,并且“if”部分只读取(原子步骤),并且“i = 2”(另一个原子步骤),那么我不需要锁定?

2 个答案:

答案 0 :(得分:2)

原子操作本质上是不可中断的 - 只有在整个操作之前或之后,其他任何人都无法看到系统处于不一致状态。

单个读取或写入原始值是原子FOR THAT CORE。如果代码在其他处理器内核中运行,则可能不安全;在操作系统显式执行该操作之前,值通常不会从处理器高速缓存中刷新并使其他内核可见。我不太了解C#线程,知道那里存在多大的风险。

读取 - 修改 - 写入,这是我们经常关注的,通常不是 - 包括++和 - 操作。在某些体系结构中,有单个指令实现了一个固有的原子测试和设置,这是一个读 - 修改 - 写的专用案例,可用于实现一个非常轻量级的线程安全锁定/计数....但这在很大程度上取决于你正在运行的处理器。

涉及读取和/或更改多个原始值的操作,或者对值执行非常重要操作的操作,不会是原子的,也不会是如何切片的。大多数实现原始信号量的实际任务确实涉及多个必须保持彼此同步的值。因此,虽然知道某些操作是原子操作对于最简单的情况是有帮助的,但是一旦超出最低级别的子例程,您就会发现这还不够。此时,您需要使用显式锁来防止其他线程中断聚合操作。信号量/锁基本上使用原始原子作为更复杂操作的围栏,利用操作系统功能使第二个(或更高)线程尝试访问锁等待它非常 (基本上,睡觉直到锁被释放,而不是进入旋转循环)。这使您可以创建更大规模的操作,尽管他们必须相互合作才能实现这一点。

总结:如果你不 KNOW 某些东西本质上是原子的,那么总是假设它不是,并且如果它将从多个线程访问它必须受到保护锁。如果不这样做会导致错误的值和非常烦人的调试方案。不要冒险;如有疑问,请明确保护。

(并且不要忘记,结构是原子的并不能确保其内容是原子的。我去年必须调试一个案例,其中有人使用Java的原子集合类型之一,但忘记了它们的结构存储到集合中也必须保护自己的内容。)

答案 1 :(得分:2)

数据类型不是原子的,使用它们的某些操作是或可以是原子的。分配32位整数变量

Int32 foo = 123456789;
例如,

是原子操作。永远不会发生另一个线程观察foo为52501,即分配最低有效16位但尚未最高有效16位。对于Int64,情况也是如此 - 可能会发生另一个线程看到仅部分执行的赋值操作。

许多其他操作不是原子的。例如

foo++;

需要读取,修改和写入值,因此另一个线程可能会在您读取之后读取并更改foo的值,但在您能够写回更新的值之前。您可以使用Interlocked.Increment()以原子方式执行此操作,并且还有其他几种操作的方法。

因此,实质上你所谓的原子数据类型只能保证变量赋值是以原子方式执行的,除了DoubleDecimalInt64和{之外的原始数据类型和引用都是如此{1}}。

UInt64
相关问题