使用原子 TAS 指令实现自旋锁
使用原子 TAS 指令实现自旋锁
Implementing a Spinlock Using the Atomic TAS Instruction从零实现自旋锁:基于 TAS 的最小同步原语
Building a Spinlock from Scratch with Atomic TAS用 test-and-set 实现最简单的互斥锁
Implementing a Minimal Mutex Using Test-and-Set自旋锁的底层原理:TAS、原子性与忙等待
Inside Spinlocks: TAS, Atomicity, and Busy Waiting原子操作与自旋锁:用 C 语言实现线程同步
Atomic Operations and Spinlocks: Thread Synchronization in C从原子指令到锁:全面理解 TAS 和自旋锁
From Atomic Instructions to Locks: A Complete Guide to TAS and Spinlocks动手写一个自旋锁:tryLock / lockAcquire / lockRelease 全实现
Hands-On Spinlock Implementation: tryLock, lockAcquire, and lockRelease你的第一个自旋锁:基于 C 语言的 TAS 实现
Your First Spinlock: A TAS-Based Implementation in C原子交换与线程互斥:自旋锁实现指南
Atomic Exchange and Thread Mutual Exclusion: A Guide to Implementing Spinlocks
假设我们有一个 TAS(Take-And-Set)函数。该操作返回内存中原来的值,并以原子方式将其替换为新值。原子性(atomicity)意味着没有其他线程能够观察到中间状态;整个读-写操作是一体不可分的。
在 C++ 中,标准库函数 std::exchange 在逻辑上表现相同,但它不是原子操作。同步原语需要硬件级别的原子性。
int TAS(int* memory, int newVal) {
int old = *memory;
*memory = newVal;
return old;
}
我们想使用这个原语来实现一个简单的自旋锁,包括:
lockAcquire()lockRelease()
线程将调用这些函数来保护对共享变量的访问:
typedef struct {
int lock;
} lockType;
typedef struct {
int val;
} threadArgType;
void threadFunc(void* arg) {
lockAcquire((static_cast<lockType*>arg)->lock);
(static_cast<threadArgType*>arg)->val++;
lockRelease((static_cast<lockType*>arg)->lock);
}
实现 tryLock
tryLock 函数尝试获取锁一次。如果锁为空(值为 0),TAS 将其设置为 1 并返回原值(0)。如果锁已被占用,TAS 返回 1。tryLock 函数是非阻塞的——它会立即返回。
因此 tryLock() 只有在 TAS 返回 0 时才会成功:
enum {
UNLOCKED = 0,
LOCKED = 1
}
int tryLock(lockType* lock) {
// 如果之前已锁定返回 1,如果之前未锁定返回 0
int old = TAS(lock->lock, LOCKED);
return (old == UNLOCKED); // true (1) = 成功获取锁
}
实现 lockAcquire()
普通的锁获取应当“自旋”直到 tryLock() 成功。这称为 自旋锁,因为 CPU 会忙等待。必要时可以加入短暂的 sleep。例如,sleep(0) 并不会真正暂停执行,而是让出 CPU,允许其他线程运行。
它通常用于实现跨线程的互斥自旋锁。
void lockAcquire(lockType* lock) {
while (!tryLock(lockType* lock)) {
// 自旋直到锁可用
}
}
另一种实现:
void lockAcquire(lockType* lock) {
do {
if (tryLock(lockType* lock)) {
break;
}
} while (1);
}
展开 tryLock:
void lockAcquire(lockType* lock) {
do {
int old = TAS(lock->lock, LOCKED);
// 无论锁是否已被获取,锁都已设置为 LOCKED
if (old == UNLOCKED) {
break;
}
} while (1);
}
这是使用 TAS 实现的最简单方法。在实际系统中,我们可能会加入 pause 指令或退避策略,但基本思路是相同的。
实现 lockRelease()
释放锁时,持有者只需将锁变量写为 0。由于 TAS 是“设置新值并返回旧值”,它同样适用于释放锁:
void lockRelease(lockType* lock) {
TAS(lock->lock, UNLOCKED);
}
或者使用简单的原子存储也足够,但由于 TAS 是我们唯一的工具,我们重用它。请注意,在这里重复释放锁是安全的,因为再次将其设置为 UNLOCKED=0 不会产生副作用。
总结
仅使用原子 TAS 指令,我们实现了:
- 一个
tryLock()尝试 - 一个
lockAcquire()自旋锁 - 一个
lockRelease()解锁操作
这种锁的实现方式对于理解低级并发、内存顺序以及高层互斥锁库的构建方式非常基础。
C/C++编程
- 理解C++中的std::transform_reduce及示例
- 使用原子 TAS 指令实现自旋锁
- C++中检测编译时与运行时: if consteval 与 std::is_constant_evaluated()
- C++ 转发引用: 完美转发的关键
- 理解 C++ 中的 dynamic_cast: 安全的向下转型与向上转型
- C与C++: restrict关键字及其在编译器优化中的作用
- C++的左值/lvalue, 右值/rvalue和右值引用/rvalue references
- C++中的assert和static_assert的区别
- C++: auto_ptr智能指针被弃用
- C++中的consteval是什么? 它与const和constexpr有何不同?
- C++ 教程: 用std::move来移动所有权
- C++中的 const和constexpr 比较
- 简易教程: C++的智能指针
- C++ 编程练习题: 如何合并两个二叉树?
- C++ 编程练习题 - 找出第三大的数
- C++ 编程练习题 - 最多连续的 1
- C++ 编程练习题 - 左子树叶节点之和 (深度优先+广度优先+递归)
- C++ 编程练习题 - 最多水容器 (递归)
- C++的异步编程: std::future, std::async 和 std::promise
- C编程练习题: 翻转整数位
- C++编程练习题: 找出字符串的所有大小小组合
- C/C++ 中的内存管理器(堆与栈)
- C++编程练习题: 对两单向链表求和
英文:Implement a Lock Acquire and Release in C++
强烈推荐
- 英国代购-畅购英伦
- TopCashBack 返现 (英国购物必备, 积少成多, 我2年来一共得了3000多英镑)
- Quidco 返现 (也是很不错的英国返现网站, 返现率高)
- 注册就送10美元, 免费使用2个月的 DigitalOcean 云主机(性价比超高, 每月只需5美元)
- 注册就送10美元, 免费使用4个月的 Vultr 云主机(性价比超高, 每月只需2.5美元)
- 注册就送10美元, 免费使用2个月的 阿里 云主机(性价比超高, 每月只需4.5美元)
- 注册就送20美元, 免费使用4个月的 Linode 云主机(性价比超高, 每月只需5美元) (折扣码: PodCastInit2022)
- PlusNet 英国光纤(超快, 超划算! 用户名 doctorlai)
- 刷了美国运通信用卡一年得到的积分 换了 485英镑
- 注册就送50英镑 – 英国最便宜最划算的电气提供商
- 能把比特币莱特币变现的银行卡! 不需要手续费就可以把虚拟货币法币兑换
微信公众号: 小赖子的英国生活和资讯 JustYYUK