我正在努力使“原子与非原子”的概念在我的脑海中得到解决。我的第一个问题是我找不到“现实生活中的类比”。喜欢客户/餐馆之间的原子操作关系或类似的东西。
另外,我想了解原子操作是如何将自己置于线程安全编程中的。
在这篇博客文章中,http://preshing.com/20130618/atomic-vs-non-atomic-operations/被提到如下:
对于共享内存的操作,如果在相对于其他线程的单个步骤中完成,则为原子操作。当在共享变量上执行原子存储时,没有其他线程能够观察到修改半完全的。当对共享变量执行原子加载时,它会读取整个值,就像它在某个时刻出现的那样。非原子负载和存储不提供这些保证。
什么是“没有其他线程可以观察修改半完成”?
这意味着线程会等到原子操作完成?那个线程是如何知道这个操作是原子的?例如,在.NET中,我可以理解,如果锁定对象,就会设置一个标志来阻止其他线程。那原子呢?其他线程如何区分原子操作和非原子操作?
另外,如果上述语句为真,是否所有原子操作都是线程安全的?
发布于 2016-09-30 16:36:36
让我们稍微澄清一下什么是原子和什么是块。原子性意味着操作要么完全执行,所有副作用都是可见的,要么根本不执行。因此,所有其他线程都可以在操作之前或之后看到状态。由互斥保护的代码块也是原子的,我们只是不称它为操作。原子操作是特殊的CPU指令,在概念上类似于通常由互斥体保护的操作(您知道互斥锁是什么,所以我将使用它,尽管它是使用原子操作实现的)。CPU有一组有限的操作,可以原子地执行,但是由于硬件的支持,它们非常快。
当我们讨论线程块时,我们通常会在对话中涉及互斥,因为由它们保护的代码可能需要相当长的时间来执行。所以我们说线程等待互斥。因为原子操作的情况是一样的,但是它们是快速的,我们通常不关心这里的延迟,所以不太可能同时听到“块”和“原子操作”这两个词。
这意味着线程会等到原子操作完成?
是的,会等的。CPU将限制对变量所在的内存块的访问,而其他CPU核心将等待。请注意,由于性能原因,块只保留在原子操作本身之间。CPU内核允许缓存读取变量。
那个线程是如何知道这个操作是原子的?
使用特殊的CPU指令。您的程序中刚刚写到,应该以原子的方式执行特定的操作。
附加信息:
原子操作还有更复杂的部分。例如,在现代CPU上,通常所有原始类型的读和写都是原子的。但是CPU和编译器可以重新排序。因此,您可能会更改一些struct,设置一个指示已更改的标志,但在结构实际提交到内存之前,CPU会重新排序写入和设置标志。当您使用原子操作时,通常会做一些额外的工作来防止不想要的重新排序。如果你想知道更多,你应该阅读有关记忆障碍。
简单的原子存储和写并不那么有用。要最大限度地利用原子操作,您需要更复杂的操作。最常见的是CAS -比较和交换。您将变量与值进行比较,并且只有在比较成功时才更改它。
发布于 2016-09-30 16:59:36
在典型的现代CPU上,原子操作以这种方式变成原子操作:
当发出访问内存的指令时,核心的逻辑试图将核心的缓存置于正确的状态以访问该内存。通常,这种状态将在必须进行内存访问之前实现,因此不存在延迟。
当另一个核心对一块内存执行原子操作时,它会将该内存锁定在自己的缓存中。这将阻止任何其他内核在原子操作完成之前获得访问该内存的权限。
除非两个核心碰巧执行对许多相同内存区域的访问,而且其中许多访问是写的,否则这通常不会涉及任何延迟。这是因为原子操作非常快,而且核心通常提前知道它需要访问什么内存。
因此,假设最近一次访问核心1上的内存块,现在核心2想做原子增量。当核心的预取逻辑在指令流中看到对该内存的修改时,它将指示缓存来获取该内存。缓存将使用内核总线从核心1的缓存中获取该内存区域的所有权,并将该区域锁定在自己的缓存中。
此时,如果另一个内核试图读取或修改该内存区域,则在释放锁之前,它将无法在其缓存中获取该区域。这种通信是在连接缓存的总线上进行的,其发生的确切位置取决于内存所在的缓存。(如果根本不在缓存中,则必须转到主内存。)
缓存锁通常不被描述为阻塞线程,这是因为它速度太快,而且内核通常在试图获取锁定在另一个缓存中的内存区域时能够做其他事情。从高级代码的角度来看,atomics的实现通常被认为是实现细节。
所有原子操作都保证不会看到中间结果。所以它们才是原子的。
发布于 2016-09-30 15:52:27
您描述的原子操作是处理器和硬件中的指令,在完成原子写入之前,硬件将确保在内存位置不会发生读取。这可以保证线程在写之前读取值,或者在写操作之后读取值,但是没有中间的任何东西--没有机会从写入之前读取值的一半字节,从写入后读取另一半字节。
运行在处理器上的代码甚至不知道这个块,但是它实际上与使用lock语句来确保一个更复杂的操作(由许多低级指令组成)是原子操作没有什么区别。
单个原子操作始终是线程安全的--硬件保证操作的效果是原子的--它不会在中间被中断。
在绝大多数情况下,一组原子操作并不是原子操作(我不是专家,所以我不想做明确的陈述,但我想不出有什么不同的情况)--这就是为什么复杂的操作需要锁定:整个操作可能由多个原子指令组成,但整个操作仍可能在这两个指令之间被中断,从而造成另一个线程看到半成品的可能性。锁定确保在共享数据上操作的代码在其他操作完成之前无法访问该数据(可能通过多个线程开关)。
this question / answer中显示了一些示例,但是您可以通过搜索找到更多的示例。
https://stackoverflow.com/questions/39795265
复制相似问题