Language agnostic 原子指令
你说的原子指令是什么意思 以下内容是如何变成原子的 测试集Language agnostic 原子指令,language-agnostic,synchronization,nonblocking,atomicity,test-and-set,Language Agnostic,Synchronization,Nonblocking,Atomicity,Test And Set,你说的原子指令是什么意思 以下内容是如何变成原子的 测试集 int TestAndSet(int *x){ register int temp = *x; *x = 1; return temp; } 从软件的角度来看,如果不想使用非阻塞同步原语,如何确保指令的原子性?是否只可能在硬件或某些汇编级指令优化可以使用?原子性只能由操作系统保证。操作系统使用底层处理器功能来实现这一点 因此,创建自己的testandset函数是不可能的。(虽然我不确定是否可以使用内联asm代码段,并
int TestAndSet(int *x){
register int temp = *x;
*x = 1;
return temp;
}
从软件的角度来看,如果不想使用非阻塞同步原语,如何确保指令的原子性?是否只可能在硬件或某些汇编级指令优化可以使用?原子性只能由操作系统保证。操作系统使用底层处理器功能来实现这一点 因此,创建自己的testandset函数是不可能的。(虽然我不确定是否可以使用内联asm代码段,并直接使用testandset助记符(可能是该语句只能通过操作系统权限完成)) 编辑: 根据本文下面的评论,直接使用ASM指令生成您自己的“BiteStandset”函数是可能的(在英特尔x86上)。然而,这些技巧是否也适用于其他处理器尚不清楚
我坚持我的观点:如果你想做atmoic的事情,使用操作系统功能,而不是自己去做一些机器指令本质上是原子的——例如,在许多体系结构上,读取和写入正确对齐的本机处理器字大小值是原子的 这意味着硬件中断,其他处理器和超线程不能中断读取或存储,也不能向同一位置读取或写入部分值 更复杂的事情,如读写一起原子可以通过显式的原子机器指令实现,例如x86上的LOCK CMPXCHG 锁定和其他高级构造构建在这些原子原语之上,这些原语通常只保护单个处理器字
一些聪明的并发算法可以通过读取和写入指针来构建,例如在单个读写器之间共享的链表中,或者通过努力在多个读写器之间共享。当您有任何形式的并行处理(包括不同的应用程序协作或共享数据)时,原子性是一个关键概念这包括共享资源 通过一个例子很好地说明了这个问题。假设您有两个程序想要创建一个文件,但前提是该文件不存在。这两个程序中的任何一个都可以在任何时间点创建文件 如果您这样做(我将使用C,因为它是您示例中的内容): 您无法确定另一个程序是否在“打开以供读取”和“打开以供写入”之间创建了文件
您自己无法做到这一点,您需要操作系统的帮助,操作系统通常为此提供同步原语,或者其他保证是原子的机制(例如,锁定操作是原子的关系数据库,或者处理器“测试和设置”指令等较低级别的机制)原子语来自希腊语ἄτομος(atomos),意思是“不可分割”。(注意:我不会说希腊语,所以可能它真的是另一回事,但大多数说英语的人引用词源来解释它。:-) 在计算中,这意味着操作发生了。在完成之前没有任何可见的中间状态。因此,如果您的CPU因服务硬件(IRQ)而中断,或者如果另一个CPU正在读取相同的内存,则不会影响结果,并且这些其他操作会将其视为已完成或未启动 例如。。。假设您想将某个变量设置为某个对象,但前提是之前未设置该变量。您可能倾向于这样做:
if (foo == 0)
{
foo = some_function();
}
但如果这是并行运行的呢?这可能是程序将获取foo
,将其视为零,同时线程2出现并执行相同的操作,并将值设置为某个值。回到原始线程,代码仍然认为foo
为零,并且变量被赋值两次
对于这种情况,CPU提供一些指令,可以作为原子实体进行比较和条件赋值。因此,测试和设置、比较和交换以及加载链接/存储条件。您可以使用这些来实现锁(您的操作系统和C库已经实现了这一点),或者您可以编写依赖原语来完成某些操作的一次性算法。(这里有一些很酷的事情可以做,但大多数凡人都会避免这样做,因为他们害怕弄错。)下面是我关于原子性的一些注释,可能会帮助你理解原子性的含义。这些注释来自最后列出的来源,如果你需要更透彻的解释,而不是像我这样的点式项目符号,我建议你阅读其中的一些。请指出任何错误,以便我改正 定义:
- 源自希腊语,意思是“不能分成更小的部分”
- “原子”操作总是被观察到是否完成,但是 从不半途而废
- 原子操作必须完全执行,否则不执行 全部
- 在多线程场景中,变量从未变异变为 直接变异,没有“中途变异”值
- 考虑不同线程使用的以下整数:
int X = 2; int Y = 1; int Z = 0; Z = X; //Thread 1 X = Y; //Thread 2
- 在上面的示例中,两个线程使用X、Y和Z
- 每次读写都是原子的
- 线程将运行:
- 如果线程1获胜,那么Z=2
- 如果线程2获胜,那么Z=1
- Z肯定是这两个值之一
- 考虑递增/递减表达式:
i++; //increment i--; //decrement
- 这些操作转化为:
- 读我
- 增加/减少读取值
- 将新值写回i
- 每个操作都由3个原子操作组成,并且本身不是原子操作
- 两次尝试在单独的线程上递增i
i++; //increment i--; //decrement
struct MyLong { public readonly int low; public readonly int high; public MyLong(int low, int high) { this.low = low; this.high = high; } }
MyLong X = new MyLong(0xAAAA, 0xAAAA); MyLong Y = new MyLong(0xBBBB, 0xBBBB); MyLong Z = new MyLong(0xCCCC, 0xCCCC);
X = Y; //Thread 1 Y = X; //Thread 2
X.low = Y.low; //Thread 1 - X = 0xAAAABBBB Y.low = Z.low; //Thread 2 - Y = 0xCCCCBBBB Y.high = Z.high; //Thread 2 - Y = 0xCCCCCCCC X.high = Y.high; //Thread 1 - X = 0xCCCCBBBB <-- corrupt value for X
using System.Threading; int unsafeCount; int safeCount; unsafeCount++; Interlocked.Increment(ref safeCount);
std::atomic< int> value; void increment(){ ++value; } void decrement(){ --value; } int get(){ return value.load(); }
import java.util.concurrent.atomic.AtomicInteger; public class Counter { private AtomicInteger value= new AtomicInteger(); public int increment(){ return value.incrementAndGet(); } public int getValue(){ return value.get(); } }