为什么使用 CAS
随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略: 先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。
乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,
因为互斥锁只能防止其他线程进入临界区,但不能防止操作在执行过程中被系统中断或其他硬件事件打断。而硬件支持的原子操作可以保证在整个操作过程中,操作是完全不可分割的,确保了即使在高度并发的环境中也能保持一致性和稳定性。
所以只能靠硬件来完成。硬件支持的原子性操作最典型的是: 比较并交换(Compare-and-Swap,CAS)。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。
使用场景
示例:银行账户的并发转账
假设有一个简单的在线银行应用,它允许用户在不同账户之间进行资金转账。考虑一个场景,其中两个不同的用户几乎同时从同一个账户A向不同的账户B和C转账。
仅使用互斥同步机制(可能存在问题)
- 步骤:
- 用户1请求从账户A向账户B转移100元。
- 用户2几乎同时请求从账户A向账户C转移50元。
- 互斥同步的实现:
- 使用互斥锁来确保在任一时刻只有一个转账操作可以访问账户A的余额。
- 首先,用户1的操作获得锁,开始处理转账。
- 用户1的操作读取账户A的余额,假设为200元。
- 中断和问题:
- 在用户1的操作完成之前,由于某种原因(例如系统调度、线程切换),该操作被中断。
- 用户2的操作此时获得锁,开始处理转账。
- 用户2的操作也读取账户A的余额,仍然是200元(因为用户1的操作尚未完成)。
结果是,两个操作都认为账户A有足够的余额进行转账,可能导致账户A的余额变成负值,这在现实世界是不可接受的。
使用硬件支持的原子操作
- 原子操作的实现:
- 使用像“比较并交换”(Compare and Swap, CAS)这样的硬件支持的原子操作来处理转账。
- 这样的操作会在一个不可分割的步骤中检查账户余额,并在足够的情况下立即执行转账。
- 执行流程:
- 当用户1的操作尝试进行转账时,CAS指令会检查账户A的余额,并在确认足够后立即扣减100元。
- 如果用户2的操作几乎同时发生,CAS指令也会尝试执行。
- 但是,如果账户A的余额不足以支持第二次转账,CAS操作将失败,不会执行转账。
通过这种方式,硬件级的原子操作确保了即使在高度并发的场况下,每次检查和修改账户余额的操作都是不可分割的,从而避免了数据不一致的问题。
结论
这个例子展示了在复杂的并发环境中,仅依靠互斥同步机制可能无法有效地解决所有问题,特别是在需要细粒度控制和高性能的场景下。硬件支持的原子操作提供了一种更加可靠和高效的方式来确保操作的原子性,特别是在涉及共享资源的冲突检测和处理时。