原子操作会阻止其他线程吗?

Teo*_*ahi 4 concurrency multithreading atomic nonatomic

我试图让"原子与非原子"概念在我的脑海中解决.我的第一个问题是我找不到"现实生活中的类比".就像原子操作上的客户/餐馆关系或类似的东西.

另外,我想了解原子操作如何将自己置于线程安全编程中.

在这篇博文中; http://preshing.com/20130618/atomic-vs-non-atomic-operations/ 它被提及为:

如果相对于其他线程在一个步骤中完成,则对共享内存执行的操作是原子操作.当对共享变量执行原子存储时,没有其他线程可以观察到修改半完成.当对共享变量执行原子加载时,它会读取单个时刻出现的整个值.非原子载荷和商店不做出这些保证.

"没有其他线程可以观察修改半完成"是什么意思?

这意味着线程将等待直到原子操作完成?该线程如何知道该操作是原子的?例如在.NET中我可以理解,如果你锁定对象,你设置一个标志来阻止其他线程.但原子怎么样?其他线程如何知道原子操作和非原子操作之间的区别?

如果上面的语句为真,那么所有原子操作都是线程安全的吗?

Ale*_*nov 11

让我们澄清什么是原子和什么是块.原子性意味着操作要么完全执行,所有的副作用都是可见的,要么根本不执行.因此,所有其他线程可以在操作之前或之后查看状态.由互斥锁保护的代码块也是原子的,我们只是不称它为操作.原子操作是特殊的CPU指令,在概念上类似于互斥锁保护的常规操作(你知道互斥锁是什么,所以我会使用它,尽管它是使用原子操作实现的).CPU具有一组有限的操作,可以自动执行,但由于硬件支持,它们非常快.

当我们讨论线程块时,我们通常会在对话中涉及互斥锁,因为由它们保护的代码可能需要相当长的时间才能执行.所以我们说线程在互斥上等待.对于原子操作情况是相同的,但是它们很快并且我们通常不关心这里的延迟,因此不太可能听到单词"阻塞"和"原子操作"在一起.

这意味着线程将等待直到原子操作完成?

是的它会等 CPU将限制对变量所在的内存块的访问,并且其他CPU内核将等待.请注意,出于性能原因,块仅保留在原子操作本身之间.允许CPU核心缓存变量以供读取.

该线程如何知道该操作是原子的?

使用特殊的CPU指令.只是在程序中写入了特定的操作应该以原子方式执行.

附加信息:

原子操作有更棘手的部分.例如,在现代CPU上,通常所有原始类型的读写都是原子的.但是CPU和编译器可以重新排序.因此,您可能更改某个结构,设置一个标志,告知它已更改,但CPU重新排序在结构实际提交到内存之前写入并设置标志.当您使用原子操作时,通常会做一些额外的工作来防止意外的重新排序.如果你想了解更多,你应该阅读内存障碍.

简单的原子存储和写入并不是那么有用.要最大限度地利用原子操作,您需要更复杂的东西.最常见的是CAS - 比较和交换.您将变量与值进行比较,并仅在比较成功时更改它.


Dav*_*rtz 7

在典型的现代 CPU 上,原子操作是通过这种方式实现的:

当发出访问内存的指令时,内核的逻辑会尝试将内核的缓存置于正确的状态以访问该内存。通常,此状态将在必须发生内存访问之前实现,因此没有延迟。

当另一个内核对一块内存执行原子操作时,它会将该内存锁定在自己的缓存中。这可以防止任何其他内核在原子操作完成之前获得访问该内存的权限。

除非两个内核碰巧对许多相同的内存区域执行访问并且其中许多访问是写操作,否则这通常根本不会涉及任何延迟。那是因为原子操作非常快,并且通常核心提前知道它需要访问哪些内存。

因此,假设上次在核心 1 上访问了一块内存,现在核心 2 想要进行原子增量。当内核的预取逻辑在指令流中看到对该内存的修改时,它将指示缓存获取该内存。缓存将使用内核间总线从内核 1 的缓存中获取该内存区域的所有权,并将该区域锁定在自己的缓存中。

此时,如果另一个内核尝试读取或修改该内存区域,则在释放锁之前,它将无法在其缓存中获取该区域。这种通信发生在连接缓存的总线上,它发生的确切位置取决于内存所在的缓存。(如果根本不在缓存中,那么它必须转到主内存。)

缓存锁通常不会被描述为阻塞线程,因为它非常快,而且内核在尝试获取锁定在另一个缓存中的内存区域时通常能够做其他事情。从高层代码的角度来看,原子的实现通常被认为是一个实现细节。

所有原子操作都保证不会看到中间结果。这就是使它们具有原子性的原因。