原子操作从硬件保证某些从语义上看起来需要多次操作的行为可以只通过一条处理器指令就能完成
创始人
2024-06-01 20:04:38
0

这五个指令都是用来实现原子操作的,也就是在并发编程中保证线程安全的操作。

具体来说:

  1. 测试并设置(Test-and-Set)指令是指在执行写操作之前,先读取一个值,并将这个值和一个预设的值进行比较,如果相等,则将一个新值写入该位置。这个指令常用于实现锁的获取操作,通过原子地测试并设置一个标志位,判断锁是否被占用,并将锁的状态设置为占用。

  2. 获取并增加(Fetch-and-Increment)指令是指将一个内存位置的值加上一个给定的值,并返回加之前的原值。这个指令常用于实现计数器等数据结构,保证在并发修改时能够正确地增加计数器的值。

  3. 交换(Swap)指令是指将一个内存位置的值和一个给定的值进行交换,并返回原来的值。这个指令常用于实现互斥量(Mutex)等同步原语,可以保证在多个线程之间交换互斥量的状态,从而实现同步。

  4. 比较并交换(Compare-and-Swap,CAS)指令是指读取一个内存位置的值,与一个期望值进行比较,如果相等则将该位置的值更新为一个新值。CAS指令是实现乐观锁的基础,可以用来解决多个线程同时修改一个变量时出现的竞态条件问题。

  5. 加载链接/条件储存(Load-Linked/Store-Conditional,LL/SC)指令是一对指令,LL指令用来读取内存中的值,并在处理器缓存中建立一个链接,SC指令用来检查缓存中链接对应的内存位置是否被修改过,如果没有则将新值写入该位置。LL/SC指令是实现一些复杂同步原语的基础,例如基于事务的内存模型。

测试并设置(Test-and-Set) 和 比较并交换(Compare-and-Swap,CAS)

测试并设置(Test-and-Set)和比较并交换(Compare-and-Swap,CAS)两种操作都是用于实现同步原语的硬件支持指令,但它们是有区别的。

Test-and-Set操作会原子性地将指定的内存位置设置为“已锁定”,并返回该位置在操作前的值。如果操作前的值为“已锁定”,则该操作会一直阻塞直到该位置变为“未锁定”。这种操作在多线程环境中可以用于实现互斥锁,即一个线程获取到锁之后,其他线程都必须等待该线程释放锁之后才能获取锁。

而CAS操作也是一种原子操作,它将指定内存位置的值与期望的值进行比较,如果相等则将该位置的值更新为新值,并返回更新前的旧值;否则不做任何操作,返回当前位置的值。这种操作在多线程环境中可以用于实现无锁算法,即多个线程可以同时访问共享资源而不需要使用锁进行同步,从而提高并发性能。

因此,虽然Test-and-Set和CAS操作都是用于实现同步原语的硬件支持指令,但它们的应用场景和使用方式是不同的。

交换(Swap)是什么?

交换(Swap)指的是将两个变量的值进行交换,通常需要使用一个中间变量来完成。而在硬件层面,交换指令可以直接完成这个操作,而且是原子性的。

下面是一个使用交换指令进行变量交换的示例代码:

public class SwapExample {private static int x = 10;private static int y = 20;public static void main(String[] args) {System.out.println("Before swap: x=" + x + ", y=" + y);swap();System.out.println("After swap: x=" + x + ", y=" + y);}private static void swap() {// 使用交换指令交换x和y的值int tmp = x;x = y;y = tmp;}
}

输出结果为:

Before swap: x=10, y=20
After swap: x=20, y=10

可以看到,使用交换指令可以非常方便地完成变量交换的操作。

交换(Swap)指令常用于实现互斥量等同步原语,因为它可以实现原子性操作,即在多个线程之间交换互斥量的状态时,保证操作的原子性和线程安全性。

考虑使用交换指令来实现互斥量的例子,假设有两个线程 A 和 B,它们都需要访问一个共享资源。在代码中,使用一个整型变量 mutex 来表示互斥量,当 mutex 的值为 0 时,表示共享资源没有被占用,可以被线程 A 占用,当 mutex 的值为 1 时,表示共享资源已经被占用,线程 A 必须等待线程 B 释放该资源后才能继续访问。

使用交换指令实现互斥量的代码如下:

int mutex = 0; // 初始值为 0// 线程 A 访问共享资源
while (true) {while (mutex == 1); // 等待共享资源被释放if (compareAndSwap(&mutex, 0, 1)) { // 使用交换指令尝试占用互斥量// 访问共享资源...// 释放互斥量swap(&mutex, 0); // 使用交换指令释放互斥量break;}
}// 线程 B 访问共享资源
while (true) {while (mutex == 0); // 等待共享资源被占用if (compareAndSwap(&mutex, 1, 0)) { // 使用交换指令尝试释放互斥量// 释放共享资源...break;}
}

在上述代码中,compareAndSwap() 函数是比较并交换指令,用于尝试占用或释放互斥量。当 mutex 的值为 0 时,compareAndSwap() 将 mutex 的值设置为 1,并返回原来的值;当 mutex 的值为 1 时,compareAndSwap() 不会修改 mutex 的值,并返回原来的值。通过不断地使用交换指令和比较并交换指令,可以实现线程间对共享资源的互斥访问,从而保证同步和线程安全性。

加载链接/条件储存(Load-Linked/Store-Conditional,LL/SC)指令是一对指令举例说明:

import java.util.concurrent.atomic.AtomicReference;public class SpinLock {private AtomicReference owner = new AtomicReference();private int count = 0;public void lock() {Thread current = Thread.currentThread();if (owner.get() == current) {++count;return;}while (!owner.compareAndSet(null, current)) {// spin}}public void unlock() {Thread current = Thread.currentThread();if (owner.get() != current) {throw new IllegalMonitorStateException("Calling thread has not locked this lock");}if (--count == 0) {owner.set(null);}}
}

LL/SC指令通常是在处理器的指令集中实现的,因此具体实现方式可能有所不同。下面是一个简单的示例,使用LL/SC指令来实现一个简单的自旋锁:

import java.util.concurrent.atomic.AtomicReference;public class SpinLock {private AtomicReference owner = new AtomicReference();private int count = 0;public void lock() {Thread current = Thread.currentThread();if (owner.get() == current) {++count;return;}while (!owner.compareAndSet(null, current)) {// spin}}public void unlock() {Thread current = Thread.currentThread();if (owner.get() != current) {throw new IllegalMonitorStateException("Calling thread has not locked this lock");}if (--count == 0) {owner.set(null);}}
}

这个自旋锁使用一个AtomicReference对象来存储锁的拥有者线程。当线程尝试获取锁时,它会先检查锁是否已经被当前线程持有,如果是,则直接增加计数器;否则,它会自旋地调用owner.compareAndSet(null, current)方法来尝试获取锁。该方法使用了LL/SC指令,它会先读取owner的值,并在处理器缓存中建立一个链接。然后,它会检查该值是否为null,如果是,则将其设置为当前线程并返回true,否则返回false。如果返回false,则说明有其他线程已经持有了锁,当前线程需要自旋等待。

当线程释放锁时,它会先检查锁是否已经被当前线程持有,如果不是,则抛出一个IllegalMonitorStateException异常。否则,它会将计数器减1。如果计数器为0,则说明锁已经完全释放,此时将owner设置为null。注意,这里不需要使用LL/SC指令,因为这个操作只需要在当前线程的处理器缓存中进行,不需要和其他处理器进行通信。

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...