互斥锁、自旋锁和原子操作

互斥锁和自旋锁
用法上是相似的,具有数据独占的效果;
但是自旋,代表CPU空转,不会直接进入阻塞状态,在linux C++中采用退避策略多次循环空转后未能获取锁后,最后可能移交控制权,线程进入阻塞(上下文线程切换),场景:临界区小,锁内容小。
互斥锁,该线程未获得锁时,直接进入阻塞,等到获得锁时再次唤醒,场景:临界区大,锁内容大(红黑树添加,涉及大量调整)

**读写锁 ** shared_lock c++17:对于多个线程的读写共享内存任务,互斥锁就很臃肿,尤其时多个线程读时,因此,出现这个锁。上述情景中,写具有独占性,而读应该是共享的,读时不能写,反之同理;此外,需要一个机制,读时若有线程需要写,进入等待,则也禁止其他读线程,放置读过程的独占资源(写饿死)。

原子操作是一种在多线程中,不会被线程调度、信号中断和上下文切换所打断的轻量级无锁操作,只能查看原子操作前后的状态而没有中间态,避免了数据竞争;;C++中在中提供了std::atomic,支持所有基础整数类型和指针类型,其中Atomic_flag (值为t/f)是标准库中,保证无锁实现原子操作的;其他原子类型都可能使用锁的,is_lock_free()针对某个具体的原子对象做查询是否使用锁;常见原子操作有load 读取; store 写入 ; exchange 交换并返回旧值; caompare_exchange_weak/strong(,)CAS操作;fetch_add/fetch_sub()自加自减;

CAS机制 一种多线程中的无锁原子操作,通过比较与交换,实现乐观锁的一种方式,避免锁的开销(悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题,所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源是否被其它线程修改了)
缺点:保证了单一变量的原子性,对于涉及变量间运算的临界区是无法实现原子性的。
过程:比较内存数据是否等于预期值;是,用新值更新内存数据;否,重写预期值,回到第一步;

锁和原子操作,都避免了数据竞争并实现了数据同步。锁主要针对多个变量,而原子操作只针对单个变量。单个操作;

互斥锁的底层实现:在 Linux 上,std::mutex 是一条标准库封装 → glibc pthread_mutex → 内核 futex​ 的三层链路,核心思想是:无竞争时完全在用户态用原子指令解决,只有真的抢不到锁才陷入内核​ 。
层次结构大致如下:

C++ 代码
  ↓ std::mutex::lock()
glibc: pthread_mutex_lock()
  ├─ Fast Path: 原子 CAS(用户态,无系统调用)  ← 大多数情况走这里
  └─ Slow Path: futex(FUTEX_WAIT) → 内核态      ← 发生竞争才走这里
  ↓
Linux 内核: futex_wait() → 等待队列 → schedule() → 上下文切换

Fast Path:用户态原子抢锁,线程调用 lock() 时,第一步是用一条原子 CAS 指令(cmpxchg)尝试把锁状态从 0(未锁)改成 1(已锁):如果成功进入临界区;否则进入Slow Path;;
Slow Path 先短自旋:在用户态循环检查锁状态若干次,赌持锁线程马上释放,避免立刻上下文切换; 再标记"有等待者",把锁状态改成2(CONTENDED,已锁且有等待者),告诉未来的解锁者"释放时要唤醒别人"。最后,futex(FUTEX_WAIT) 系统调用:陷入内核,内核把当前线程挂到该futex地址对应的等待队列上,线程进入睡眠,让出 CPU。(futex中把"检查值 + 睡眠"合并成一个原子的内核操作,防止检查完到睡眠之间可能恰好有人释放了)

unlock:用户态释放 + 必要时唤醒:只有当确实有线程在等时,解锁才会陷入内核。无竞争时 unlock 同样是零系统调用的纯用户态操作 。

持锁线程调用 unlock():
  1. 原子地把 state 从 1 改回 0
  2. 检查 FUTEX_WAITERS 位是否被设置
     ├─ 没设置:直接返回,无系统调用  ← 快路径
     └─ 设置了:futex(FUTEX_WAKE, 1) 唤醒一个等待线程

内核侧:内核维护一张哈希表,key 是 futex word 的用户空间地址,value 是挂在该地址上的等待线程链表。 FUTEX_WAIT:读 *uaddr,若等于期望值则在哈希桶里建一个包含线程描述符的futex_q 节点,把当前线程设为 TASK_INTERRUPTIBLE 并 schedule() 出去。(schedule内核调度器(scheduler)将当前线程从运行态切换为睡眠态,并选择另一个可运行的线程来占用 CPU。)这里再次应用CAS机制如下:

int observed = atomic_load(&lock);          // 读取当前锁状态
futex(&lock, FUTEX_WAIT, observed, NULL); 

FUTEX_WAKE:在哈希桶里找到等在这个地址上的线程,唤醒指定数量(通常 1 个)。
被唤醒的线程从 futex 系统调用返回用户态,再次 CAS 抢锁,成功则进入临界区。

下面是使用自旋锁的一个在多线程中共享一个数据的demo

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define THREAD_COUNT 10
#define ITERATIONS 10000

// static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_spinlock_t count_spin;

void *thread_callback(void *arg) {
    int *pcount = (int *)arg;
    int i = 0;
    while (i++ < ITERATIONS) {
        // pthread_mutex_lock(&count_mutex);  //这里是互斥
        pthread_spin_lock(&count_spin);
        (*pcount)++;
        usleep(100); // 模拟一些工作负载
        pthread_spin_unlock(&count_spin);
        // pthread_mutex_unlock(&count_mutex);
    }
    return NULL;
}

int main(void) {
    pthread_t threadid[THREAD_COUNT];
    int count = 0;

    if (pthread_spin_init(&count_spin, PTHREAD_PROCESS_PRIVATE) != 0) {
        perror("pthread_spin_init");
        return EXIT_FAILURE;
    }

    for (int i = 0; i < THREAD_COUNT; i++) {
        if (pthread_create(&threadid[i], NULL, thread_callback, &count) != 0) {
            perror("pthread_create");
            return EXIT_FAILURE;
        }
    }

    // for (int i = 0; i < THREAD_COUNT; i++) {
    //     if (pthread_join(threadid[i], NULL) != 0) {
    //         perror("pthread_join");
    //         return EXIT_FAILURE;
    //     }
    // }

    for (int i = 0; i < 100; i++) {
        printf("count = %d\n", count);
        sleep(1);
    }

    pthread_spin_destroy(&count_spin);
    // printf("Final count = %d\n", count);
    return EXIT_SUCCESS;
}

由于linux下提供了cas操作的接口__sync_bool_compare_and_swap:替换上面的自旋锁如下:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define THREAD_COUNT 10
#define ITERATIONS 100000

static int cas(int *addr, int expected, int desired) {
    return __sync_bool_compare_and_swap(addr, expected, desired);
}

static void atomic_increment(int *value) {
    int old_value;
    do {
        old_value = *value;
    } while (!cas(value, old_value, old_value + 1));
}

void *thread_callback(void *arg) {
    int *pcount = (int *)arg;
    for (int i = 0; i < ITERATIONS; ++i) {
        atomic_increment(pcount);
    }
    return NULL;
}

int main(void) {
    pthread_t threadid[THREAD_COUNT];
    int count = 0;

    for (int i = 0; i < THREAD_COUNT; ++i) {
        if (pthread_create(&threadid[i], NULL, thread_callback, &count) != 0) {
            perror("pthread_create");
            return EXIT_FAILURE;
        }
    }

    for (int i = 0; i < THREAD_COUNT; ++i) {
        if (pthread_join(threadid[i], NULL) != 0) {
            perror("pthread_join");
            return EXIT_FAILURE;
        }
    }

    printf("Final count = %d\n", count);
    return EXIT_SUCCESS;
}
posted @ 2026-07-23 15:31  超级麋鹿  阅读(1)  评论(0)    收藏  举报