点击↑深色口袋物联,选择关注公众号,获取更多内容,不迷路
在glibc2.35中,pthread线程提供了5种同步机制,分别为条件变量、互斥锁、读写锁、自旋锁和屏障,本篇主要回顾互斥锁
在glibc2.35中,互斥锁的定义在sysdeps/nptl/bits/pthreadtypes.h中pthread_mutex_t,其结构与条件变量类似
typedef union{ struct __pthread_mutex_s __data; char __size[__SIZEOF_PTHREAD_MUTEX_T]; long int __align;} pthread_mutex_t;其中__data是真实的数据载体,包含了操作系统所需要的全部底层信息,__size,固定大小的字符数组,保证新旧版本的二进制兼容,以及一个长整型变量,用于控制内存对齐
为什么条件变量是long long int,而互斥锁是long int?互斥锁的内部数据__data,关键字段都是32位整型,所以用 long int(通常 4 字节)。条件变量的内部数据__data,实现比互斥锁复杂得多,在现代 Linux (NPTL) 实现中,为了保证高并发下的正确性,内部使用了 64 位的序列计数器。必须进行 8 字节对齐,所以使用了 long long int(通常 8 字节)
互斥锁的核心作用只有一个:保证多个线程对「共享资源」的「排他性访问」
同一时间,只有一个线程能持有互斥锁、访问被保护的共享资源;其他线程想要访问,必须等待锁被释放,排队争抢。
简单说:互斥锁给共享资源上了一把「唯一钥匙」,谁拿到钥匙谁能用,其他人只能等,彻底杜绝多个线程同时操作共享资源导致的数据错乱。
线程的核心优势是「天然共享进程地址空间」(全局变量、堆内存、文件描述符等),但这也是最大的风险点:
count++(拆解为「读→加 1→写」3 步 CPU 指令),多线程并发时指令会互相插队,最终 count 值远小于预期;互斥锁完美解决以上问题:通过「加锁→操作资源→解锁」的原子流程,把共享资源的读写逻辑变成「串行执行」,保证数据一致性。
互斥锁是线程同步的「基础中的基础」,所有场景都围绕「保护共享资源」展开,覆盖 Linux 应用层开发 99% 的并发场景,无任何技术能替代其核心价值,按使用频率排序如下:
pthread_mutex_trylock(非阻塞)或pthread_mutex_timedlock(超时阻塞);互斥锁的接口全部在 <pthread.h> 头文件中,编译时必须加 -lpthread 链接线程库,接口数量少、逻辑简单,所有接口返回值:成功返回 0,失败返回错误码(非 errno,需手动处理)。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);mutex:待初始化的互斥锁地址;attr:互斥锁属性(如锁类型、优先级继承等),传NULL使用默认属性即可;pthread_mutex_destroy销毁!int pthread_mutex_destroy(pthread_mutex_t *mutex);int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);abstime:绝对时间(秒 + 纳秒),不是相对超时;int pthread_mutex_unlock(pthread_mutex_t *mutex);二者是「互补关系」,不是替代关系,条件变量必须依赖互斥锁使用,核心差异如下:
✅ 核心结论:互斥锁解决「能不能访问」,条件变量解决「什么时候访问」。
三种加锁方式的核心差异,按需选择即可:
✅ 选型原则:测试环境用阻塞加锁,生产环境用超时加锁,实时场景用非阻塞加锁。
自旋锁是「忙等锁」,和互斥锁的核心差异在「阻塞方式」:
✅ 选型原则:大部分场景用互斥锁,极短时间的锁竞争用自旋锁。
读写锁是「读写分离锁」,针对「多读少写」场景优化:
✅ 选型原则:读写均衡用互斥锁,多读少写用读写锁。
代码才是硬道理。具3个常用 的示例
这是最经典的用法。如果不加锁,两个线程同时改余额,最后钱肯定对不上。
#include <stdio.h>#include <stdlib.h>#include <pthread.h>int balance = 1000;pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void* withdraw(void *arg) { int amount = *(int*)arg; pthread_mutex_lock(&lock); // 1. 加锁 int temp = balance; temp -= amount; usleep(1000); // 模拟处理耗时,如果不加锁,这里肯定会乱 balance = temp; printf("取款 %d, 剩余余额: %d\n", amount, balance); pthread_mutex_unlock(&lock); // 2. 解锁 return NULL;}int main() { pthread_t t1, t2; int a1 = 200, a2 = 300; pthread_create(&t1, NULL, withdraw, &a1); pthread_create(&t2, NULL, withdraw, &a2); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("最终余额: %d (应该是 500)\n", balance); return 0;}

死锁是线程编程的大坑。这个例子展示了“固定加锁顺序”这一黄金法则。
#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>pthread_mutex_t lock_a = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_t lock_b = PTHREAD_MUTEX_INITIALIZER;// 线程1:先锁 A,再锁 Bvoid* worker1(void *arg) { printf("[线程1] 准备拿锁 A...\n"); pthread_mutex_lock(&lock_a); printf("[线程1] 拿到锁 A,干活中...\n"); sleep(1); printf("[线程1] 准备拿锁 B...\n"); pthread_mutex_lock(&lock_b); // 如果这里乱序去抢别的锁,可能死锁 printf("[线程1] 拿到锁 B,干活中...\n"); sleep(1); pthread_mutex_unlock(&lock_b); pthread_mutex_unlock(&lock_a); printf("[线程1] 完工\n"); return NULL;}// 线程2:也先锁 A,再锁 B (顺序一致,安全!)void* worker2(void *arg) { printf("[线程2] 准备拿锁 A...\n"); pthread_mutex_lock(&lock_a); printf("[线程2] 拿到锁 A,干活中...\n"); sleep(1); printf("[线程2] 准备拿锁 B...\n"); pthread_mutex_lock(&lock_b); printf("[线程2] 拿到锁 B,干活中...\n"); sleep(1); pthread_mutex_unlock(&lock_b); pthread_mutex_unlock(&lock_a); printf("[线程2] 完工\n"); return NULL;}int main() { pthread_t t1, t2; pthread_create(&t1, NULL, worker1, NULL); pthread_create(&t2, NULL, worker2, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("所有线程安全退出,无死锁\n"); return 0;}
实现「超时阻塞加锁」和「非阻塞加锁」,覆盖生产环境防卡死需求:
pthread_mutex_timedlock设置 3 秒超时,超时后执行兜底逻辑;pthread_mutex_trylock非阻塞加锁,锁被占用时直接跳过;#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>#include <time.h>#include <errno.h>pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;int g_data = 0;// 线程1:超时加锁(3秒超时)void* thread_timeout(void* arg){ struct timespec ts; // 获取当前绝对时间 + 3秒超时 clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 3; printf("线程1:尝试加锁(超时3秒)...\n"); int ret = pthread_mutex_timedlock(&g_mutex, &ts); if(ret == 0) { // 加锁成功 g_data = 100; printf("线程1:加锁成功,设置data=%d\n", g_data); sleep(5); // 持有锁5秒 pthread_mutex_unlock(&g_mutex); printf("线程1:解锁完成\n"); } else if(ret == ETIMEDOUT) { // 超时 printf("线程1:加锁超时(3秒),执行兜底逻辑\n"); } else { printf("线程1:加锁失败,错误码=%d\n", ret); } return NULL;}// 线程2:非阻塞加锁void* thread_trylock(void* arg){ for(int i=0; i<10; i++) { int ret = pthread_mutex_trylock(&g_mutex); if(ret == 0) { // 加锁成功 g_data += 10; printf("线程2:加锁成功,data=%d\n", g_data); pthread_mutex_unlock(&g_mutex); break; } else if(ret == EBUSY) { // 锁被占用 printf("线程2:锁被占用,跳过本次(第%d次尝试)\n", i+1); sleep(1); } } return NULL;}int main(){ pthread_t tid1, tid2; pthread_create(&tid1, NULL, thread_timeout, NULL); sleep(1); // 让线程1先尝试加锁 pthread_create(&tid2, NULL, thread_trylock, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_mutex_destroy(&g_mutex); printf("主线程:最终data=%d\n", g_data); return 0;}运行结果

超时加锁避免线程永久阻塞,是生产环境防卡死的核心方案;非阻塞加锁适合「不希望线程等待」的场景,如实时任务。
✔️ 互斥锁的优点是压倒性的:简单、安全、通用,是线程同步的「基石」,没有任何技术能替代;
✔️ 互斥锁的缺点都是可规避的:通过规范编码(如统一加锁顺序、减小锁粒度)、配合其他同步机制(如条件变量),能轻松解决所有局限性;
✔️ 结论:互斥锁是 Linux 线程开发「必学、必会、必用」的核心技术,没有之一。