为什么使用原子操作就不用加锁?
2026/9/6 22:52:34 网站建设 项目流程

原子操作和锁都可以保证线程安全
竞争:多线程/中断/多核CPU同时访问共享资源导致的数据不一致。

互斥锁 — pthread库

当使用pthread_mutex_lock函数时,具体流程如下

1、自旋

锁被其他线程持有,当前线程会先进行自旋(Spin),它会在一个循环里反复检查锁是否被释放,如果持有锁的线程非常快(比如执行几条指令就释放了),当前线程就不需要进入内核,直接在用户态拿到了锁,这极大地节省了开销。只有自旋了固定次数(通常几百次)依然拿不到锁,或者锁持有者被调度到别的 CPU 核心上去了,线程才会准备发起系统调用。

2、发起系统调用阻塞线程

pthread_mutex_lock 内部会调用 Linux 内核提供的 futex(Fast Userspace Mutex,快速用户空间互斥体)系统调用。执行 syscall 指令,CPU 将特权级从用户态 切换到内核态。内核栈顶指针被加载,用户态的寄存器状态被保存到内核栈中。这次切换本身有着极大的资源消耗
内核态阻塞,内核根据用户态传入的锁内存地址,在内核中找到对应的 futex 结构(等待队列)。内核将当前线程的调度状态从 R(就绪/运行) 修改为 S(可中断睡眠)。内核调用调度器,当前线程的 CPU 寄存器上下文被保存到线程的内核栈及 task_struct当中。将当前线程的等待队列节点挂载到锁的等待链表尾部。此时,CPU 会去执行其他处于 R 状态的线程。

3、其他线程pthread_mutex_unlock

unlock 内部同样会触发 futex 系统调用,告诉内核锁已释放。内核从锁的等待队列头部取出之前的线程,将其状态改回 TASK_RUNNING,并放入 CPU 的运行队列(待调度队列)。线程此时并没有立即运行,而是处于“就绪态”。必须等待当前 CPU 时间片耗尽,或者通过抢占机制,该线程才能获得 CPU。

如果是调用 pthread_mutex_trylock,它在发现锁被占用时不会调用 futex 进入内核挂起线程,而是直接在用户态返回 EBUSY 错误码。因此,trylock 的开销极低,常被用在希望“能拿就拿,拿不到就干别的”的非阻塞场景中。

互斥锁 — 内核操作

structmutex{atomic_long_towner;spinlock_twait_lock;};structmutexlock;mutex_init(&lock);//初始化互斥量mutex_lock(&lock);//加锁(可能睡眠)//临界区...mutex_unlock(&lock);//解锁

自旋锁 — 内核操作

获取锁失败时,线程进行循环检查

spinlock_tlock;spin_lock_init(&lock);//初始化自旋锁spin_lock(&lock);//加锁//临界区...spin_unlock(&lock);//解锁//1、读写自旋锁:允许多读单写rwlock_tlock;rwlock_init(&lock);//初始化读写锁read_lock(&lock);//加读锁//读操作read_unlock(&lock);//解读锁write_lock(&lock);//加写锁//写操作write_unlock(&lock);//解写锁//2、顺序锁(seqlock):写优先,适用于读多写少的场合。seqlock_init(seqlock_t*lock);SEQLOCK_UNLOCKEDwrite_seqlock(seqlock_t*lock);write_sequnlock(seqlock_t*lock);write_tryseqlock(seqlock_t*lock);write_seqlock_irqsave(lock,flags);write_sequnlock_irqrestore(lock,flags);read_seqbegin(constseqlock_t*lock);read_seqretry(constseqlock_t*lock,unsignedstart);read_seqlock_excl(seqlock_t*lock);read_sequnlock_excl(seqlock_t*lock);

原子操作 — 内核操作

不可再分割的操作,执行过程中不会被中断。
硬件层面的指令。

现代 CPU 提供了特殊的指令(ARM :LDREX/STREX)。这些指令在执行时,CPU 会锁住内存总线或使用缓存一致性协议,确保该指令的“读-改-写”过程在物理上不可打断,但是只适用于保护单变量的场景

原子操作失效的情况

转账操作-必须加锁

pthread_mutex_lock(&mutex);A_balance-=100;// 操作1B_balance+=100;// 操作2pthread_mutex_unlock(&mutex);

内核API

/**********整型操作********************************************/atomic_tv=ATOMIC_INIT(0);//定义一个原子变量V并将其初始化atomic_inc(&v);//v++atomic_dec(&v);//v--atomic_add(10,&v);//v+=10atomic_read(&v);//读取v的值atomic_set(20,&v);//v=20atomic_cmpxchg(&v,20,30);//如果v==20,则将v设为30/*************位操作*****************************************/unsignedintvalue=0;set_bit(0,&value);//设置第0位为1clear_bit(0,&value);//清除第0位,设置第0位为0test_bit(0,&value);//测试第0位change_bit(0,&value);//翻转第0位

内存屏障

原子操作需要增加内存屏障(如 memory_order_seq_cst),用于防止CPU 和编译器可能会进行指令重排,导致多核 CPU 上读到过期的缓存值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询