互斥锁、自旋锁和原子操作
互斥锁和自旋锁
用法上是相似的,具有数据独占的效果;
但是自旋,代表CPU空转,不会直接进入阻塞状态,在linux C++中采用退避策略多次循环空转后未能获取锁后,最后可能移交控制权,线程进入阻塞(上下文线程切换),场景:临界区小,锁内容小。
互斥锁,该线程未获得锁时,直接进入阻塞,等到获得锁时再次唤醒,场景:临界区大,锁内容大(红黑树添加,涉及大量调整)
**读写锁 ** shared_lock c++17:对于多个线程的读写共享内存任务,互斥锁就很臃肿,尤其时多个线程读时,因此,出现这个锁。上述情景中,写具有独占性,而读应该是共享的,读时不能写,反之同理;此外,需要一个机制,读时若有线程需要写,进入等待,则也禁止其他读线程,放置读过程的独占资源(写饿死)。
原子操作是一种在多线程中,不会被线程调度、信号中断和上下文切换所打断的轻量级无锁操作,只能查看原子操作前后的状态而没有中间态,避免了数据竞争;;C++中在
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;
}

浙公网安备 33010602011771号