小赖子的英国生活和资讯

使用原子 TAS 指令实现自旋锁

阅读 桌面完整版

使用原子 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;
}

我们想使用这个原语来实现一个简单的自旋锁,包括:

线程将调用这些函数来保护对共享变量的访问:

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 指令,我们实现了:

这种锁的实现方式对于理解低级并发、内存顺序以及高层互斥锁库的构建方式非常基础。

C/C++编程

英文:Implement a Lock Acquire and Release in C++

强烈推荐

微信公众号: 小赖子的英国生活和资讯 JustYYUK

阅读 桌面完整版
Exit mobile version