Linux C并发编程基础(线程安全)

张开发
2026/4/12 17:44:43 15 分钟阅读

分享文章

Linux C并发编程基础(线程安全)
Linux 中的线程安全Thread Safety它是多线程开发的核心痛点与必备知识点。线程安全指的是当多个线程同时访问共享资源时无论线程的执行顺序如何、是否被中断都能保证共享资源的数据一致性和程序执行结果的正确性且不会出现数据损坏、资源泄露等异常。反之若多线程访问共享资源时出现数据混乱、结果异常则称为「线程不安全」。1.线程不安全的核心根源Linux 线程共享进程的大部分资源全局变量、堆、打开的文件描述符、互斥锁等这是线程高效通信的基础也是线程不安全的根本原因具体可归结为 3 点共享资源的并发访问多个线程同时对同一共享资源如全局变量、共享缓冲区执行「写操作」或「读 写操作」纯读操作通常安全导致数据竞争Data Race。线程执行的不确定性线程是内核调度的基本单位执行顺序、执行时长由内核调度决定抢占式调度线程可能在任意指令间隙被挂起切换到其他线程执行导致共享资源的操作无法原子化。非原子操作的拆分看似简单的操作如 i在底层汇编中会被拆分为「读取 - 修改 - 写入」3 步非原子操作线程若在中间步骤被抢占会导致数据不一致。典型示例线程不安全的 i 操作#include stdio.h #include pthread.h #include unistd.h int global_count 0; // 共享全局变量 // 线程入口函数执行 100000 次 i void *thread_inc(void *arg) { for (int i 0; i 100000; i) { global_count; // 非原子操作存在数据竞争 } return NULL; } int main() { pthread_t tid1, tid2; pthread_create(tid1, NULL, thread_inc, NULL); pthread_create(tid2, NULL, thread_inc, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); // 预期结果200000实际结果小于 200000线程不安全 printf(最终 global_count%d\n, global_count); return 0; }编译运行后global_count 最终值远小于 200000这就是线程不安全的直观体现。2.线程安全的核心保障同步与互斥机制解决线程不安全问题的核心思路是通过同步与互斥机制保证共享资源的访问具有「原子性」、「可见性」和「有序性」Linux 中主要依赖 pthread 库提供的同步互斥工具最常用的有以下 4 种。2.1.互斥锁Mutex最常用的排他性同步工具互斥锁Mutual Exclusion是一种「排他锁」核心作用是保证同一时间只有一个线程能访问临界资源共享资源其他线程若想访问必须阻塞等待锁释放从而避免数据竞争。核心特性排他性锁被一个线程持有后其他线程无法获取只能阻塞等待。原子性锁的获取pthread_mutex_lock()和释放pthread_mutex_unlock()是原子操作不会被线程调度打断。可重入性默认的互斥锁非递归锁不可重入同一线程多次获取同一把锁会导致死锁递归互斥锁PTHREAD_MUTEX_RECURSIVE允许同一线程多次获取需对应多次释放。核心接口pthread 库// 1. 初始化互斥锁 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); // 2. 获取互斥锁阻塞式获取不到则阻塞等待 int pthread_mutex_lock(pthread_mutex_t *mutex); // 3. 尝试获取互斥锁非阻塞式获取不到立即返回错误不阻塞 int pthread_mutex_trylock(pthread_mutex_t *mutex); // 4. 释放互斥锁 int pthread_mutex_unlock(pthread_mutex_t *mutex); // 5. 销毁互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex);说明attr 传入 NULL 表示使用默认属性非递归、非超时互斥锁必须初始化后使用销毁前必须确保已释放。示例用互斥锁解决 i 线程不安全问题#include stdio.h #include pthread.h #include unistd.h int global_count 0; pthread_mutex_t global_mutex; // 定义互斥锁 void *thread_inc(void *arg) { for (int i 0; i 100000; i) { pthread_mutex_lock(global_mutex); // 加锁进入临界区 global_count; // 临界区操作保证原子性 pthread_mutex_unlock(global_mutex); // 解锁退出临界区 } return NULL; } int main() { pthread_t tid1, tid2; pthread_mutex_init(global_mutex, NULL); // 初始化互斥锁 pthread_create(tid1, NULL, thread_inc, NULL); pthread_create(tid2, NULL, thread_inc, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf(最终 global_count%d\n, global_count); // 预期结果200000 pthread_mutex_destroy(global_mutex); // 销毁互斥锁 return 0; }编译运行gcc thread_safe_mutex.c -o thread_safe_mutex -lpthread后global_count 最终值为 200000实现线程安全。2.2.条件变量Condition Variable线程间的协作与唤醒条件变量不具备互斥功能核心作用是实现线程间的同步协作如生产者 - 消费者模型允许一个线程等待某个「条件满足」当条件满足时由其他线程唤醒该线程继续执行避免线程通过轮询消耗 CPU 资源。核心特性依赖互斥锁条件变量必须与互斥锁配合使用防止多个线程同时修改条件、同时等待条件保证操作的原子性。阻塞与唤醒线程通过 pthread_cond_wait() 阻塞等待条件满足其他线程通过 pthread_cond_signal()唤醒一个线程或 pthread_cond_broadcast()唤醒所有等待线程通知条件满足。虚假唤醒线程可能在没有被显式唤醒的情况下被唤醒内核调度原因因此需要在循环中检查条件是否真的满足。核心接口pthread 库// 1. 初始化条件变量 int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); // 2. 阻塞等待条件满足自动释放互斥锁被唤醒后重新获取互斥锁 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); // 3. 唤醒一个等待条件的线程 int pthread_cond_signal(pthread_cond_t *cond); // 4. 唤醒所有等待条件的线程 int pthread_cond_broadcast(pthread_cond_t *cond); // 5. 销毁条件变量 int pthread_cond_destroy(pthread_cond_t *cond);典型场景生产者 - 消费者模型简单示例#include stdio.h #include pthread.h #include unistd.h #define BUFFER_SIZE 5 int buffer[BUFFER_SIZE]; int count 0; // 缓冲区数据计数 pthread_mutex_t mutex; pthread_cond_t cond_not_full; // 缓冲区未满条件 pthread_cond_t cond_not_empty; // 缓冲区非空条件 // 生产者线程向缓冲区写入数据 void *producer(void *arg) { int data 1; while (1) { pthread_mutex_lock(mutex); // 循环检查缓冲区是否已满防止虚假唤醒 while (count BUFFER_SIZE) { pthread_cond_wait(cond_not_full, mutex); } // 写入缓冲区 buffer[count] data; printf(【生产者】写入数据%d缓冲区剩余空间%d\n, style="text-align:left">2.3.自旋锁Spin Lock轻量级的忙等待锁自旋锁是一种轻量级锁核心作用与互斥锁一致排他性访问临界资源但实现方式不同当线程获取不到自旋锁时不会阻塞挂起而是在用户态循环忙等待直到锁被释放。核心特性忙等待不切换内核态无线程上下文切换开销适合临界区执行时间极短的场景。高 CPU 占用若临界区执行时间较长自旋锁会持续占用 CPU 资源导致其他线程无法执行性能劣于互斥锁。不可重入同一线程多次获取同一自旋锁会导致死锁循环等待自身释放锁。适用场景 vs 不适用场景适用临界区执行时间极短如几纳秒 / 微秒、多 CPU 核心环境、对性能要求极高的场景如内核态代码、高频访问的共享变量。不适用临界区执行时间较长、单 CPU 核心环境、IO 密集型线程会导致 CPU 空耗。核心接口pthread 库用户态// 1. 初始化自旋锁 int pthread_spin_init(pthread_spinlock_t *lock, int pshared); // 2. 获取自旋锁 int pthread_spin_lock(pthread_spinlock_t *lock); // 3. 尝试获取自旋锁非阻塞 int pthread_spin_trylock(pthread_spinlock_t *lock); // 4. 释放自旋锁 int pthread_spin_unlock(pthread_spinlock_t *lock); // 5. 销毁自旋锁 int pthread_spin_destroy(pthread_spinlock_t *lock);2.4.读写锁RW Lock区分读写的高效同步工具读写锁Reader-Writer Lock是一种优化的互斥锁核心作用是区分「读操作」和「写操作」提高多读少写场景下的并发性能。核心特性读共享多个线程可以同时获取读锁执行读操作纯读操作无数据竞争并发性能高。写排他写锁是排他锁同一时间只能有一个线程获取写锁持有写锁时其他线程无法获取读锁或写锁持有读锁时其他线程无法获取写锁。优先级默认通常是「写优先」避免写操作饥饿也可配置为「读优先」。适用场景多读少写的场景如配置文件读取、缓存数据查询、日志查询例如服务器中的配置信息大部分线程是读取配置只有少数线程在更新配置。核心接口pthread 库// 1. 初始化读写锁 int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); // 2. 获取读锁 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 3. 获取写锁 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 4. 释放读锁/写锁统一接口 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 5. 销毁读写锁 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);2.5.信号量Semaphore信号量Semaphore—— 它是 Linux 中实现线程或进程间同步与互斥的核心机制之一相比互斥锁信号量更灵活不仅能实现排他性访问互斥还能实现多线程间的资源计数与协作同步天然支持线程安全。信号量的定义与核心作用信号量本质是一个计数器 等待 / 唤醒机制用于资源计数记录可用共享资源的数量如有限的缓冲区、连接池。同步协作让线程等待某个资源可用计数器 0或通知其他线程资源已释放。互斥排他通过计数器 1 实现 “二元信号量”等效于互斥锁保证临界资源的排他性访问。信号量的分类针对线程场景Linux 中信号量分为两类线程开发优先使用「POSIX 无名信号量线程信号量」更轻量、易用核心工作原理以无名信号量为例信号量的计数器值sem决定线程行为核心逻辑如下当 sem 0表示有可用资源线程调用 sem_wait()获取资源会将计数器原子减 1直接返回并继续执行。当 sem 0表示无可用资源线程调用 sem_wait() 会阻塞挂起直到其他线程调用 sem_post()释放资源将计数器加 1 后被唤醒并完成原子减 1。sem_post() 始终是原子操作即使多个线程同时调用也不会导致计数器数据混乱保证线程安全。2.5.1.POSIX有名信号量POSIX有名信号量的一般使用步骤是1使用sem_open( )来创建或者打开一个有名信号量。2使用sem_wait( )和sem_post( )来分别进行P操作和V操作。3使用sem_close( )来关闭他。4使用sem_unlink( )来删除他并释放系统资源。示例接受数据端#include head.h int main() { //获取KEY值 key_t keyftok(./,1); //获取共享内存ID int shm_idshmget(key,1024,IPC_CREAT | 0777); //对共享内存进行映射 char *shm_addrshmat(shm_id,NULL,0); //创建或者打开有名信号量 sem_t *semsem_open(sem_name,O_CREAT,0777,0); while(1){ sem_wait(sem); printf(%s\n,shm_addr); if(strcmp(shm_addr,exit)0){ break; } } //关闭并删除信号量 sem_close(sem); sem_unlink(sem_name); return 0; }发送数据端#include head.h #define SEM_NAME sem_name int main() { //获取KEY值 key_t keyftok(./,1); //获取共享内存ID int shm_idshmget(key,1024,IPC_CREAT | 0777); //对共享内存进行映射 char *shm_addrshmat(shm_id,NULL,0); //创建或者打开有名信号量 sem_t *ssem_open(SEM_NAME,O_CREAT,0777,0); while(1){ scanf(%s,shm_addr); sem_post(s); if(strcmp(shm_addr,exit)0){ break; } } //关闭并删除信号量 sem_close(s); sem_unlink(SEM_NAME); return 0; }2.5.2.POSIX无名信号量POSIX无名信号量的使用步骤是1在这些线程都能访问到的区域定义这种变量(比如全局变量)类型是sem_t。2在任何线程使用它之前用sem_init( )初始化他。3使用sem_wait( )/sem_trywait( )和sem_post( )来分别进行P、V操作。4不再需要时使用sem_destroy()来销毁他。示例接受数据端#include head.h int main() { //获取KEY key_t keyftok(./,2); //获取共享内存ID int shm_idshmget(key,1024,IPC_CREAT | 0777); //映射共享内存 char *shm_addrshmat(shm_id,NULL,0); //初始化无名信号量 sem_t *ssem_open(sem_name,O_CREAT,0777,0); int retsem_init(s,1,0); while(1){ sem_wait(s); printf(%s\n,shm_addr); if(strcmp(shm_addr,exit)0){ break; } } //销毁无名信号量 sem_destroy(s); return 0; }发送数据端#include head.h int main() { //获取KEY key_t keyftok(./,2); //获取共享内存ID int shm_idshmget(key,1024,IPC_CREAT | 0777); //映射共享内存 char *shm_addrshmat(shm_id,NULL,0); //初始化无名信号量 sem_t *ssem_open(sem_name,O_CREAT,0777,0); int retsem_init(s,1,0); while(1){ scanf(%s,shm_addr); sem_post(s); if(strcmp(shm_addr,exit)0){ break; } } //销毁无名信号量 sem_destory(s); return 0; }2.5.3.线程安全的核心POSIX 无名信号量核心接口线程开发中semaphore.h 提供的无名信号量是首选所有接口均为线程安全编译时需链接 -lpthread 库与 pthread 线程库配套。1 初始化信号量sem_init()函数原型// 初始化无名信号量成功返回 0失败返回 -1 并设置 errno int sem_init(sem_t *sem, int pshared, unsigned int value);参数说明sem指向要初始化的信号量对象sem_t 类型。pshared是否支持跨进程共享线程场景必须设为 0表示仅同一进程内线程共享更轻量设为 1 表示支持跨进程共享需配合共享内存。value信号量初始计数器值≥0互斥场景设为 1二元信号量等效互斥锁。资源计数场景设为资源总数如缓冲区大小 5。关键注意信号量使用前必须初始化且只能初始化一次。2 获取资源阻塞式sem_wait()函数原型// 阻塞式获取信号量原子减 1成功返回 0失败返回 -1 并设置 errno int sem_wait(sem_t *sem);功能原子性检查信号量计数器若 sem 0直接将计数器减 1返回。若 sem 0线程阻塞挂起直到计数器 0其他线程调用 sem_post()被唤醒后完成减 1 并返回。支持被信号中断中断后返回 -1errno 设为 EINTR。线程安全操作是原子的多个线程同时调用不会导致计数器数据竞争。3 尝试获取资源非阻塞式sem_trywait()// 非阻塞式获取信号量原子减 1成功返回 0失败返回 -1 并设置 errno int sem_trywait(sem_t *sem);功能与 sem_wait() 类似但不阻塞若 sem 0原子减 1 并返回 0。若 sem 0立即返回 -1errno 设为 EAGAIN不阻塞线程。适用场景避免线程长时间阻塞需要轮询获取资源的场景。4 释放资源唤醒等待线程sem_post()函数原型// 释放信号量原子加 1成功返回 0失败返回 -1 并设置 errno int sem_post(sem_t *sem);功能原子性将信号量计数器加 1。若有线程因调用 sem_wait() 阻塞在该信号量上会唤醒其中一个等待线程内核调度决定让其完成 sem_wait() 的减 1 操作。关键注意仅能释放已获取的资源或初始化后的值避免计数器溢出虽无硬性限制但会导致逻辑混乱。操作是原子的支持多线程同时调用保证线程安全。5 销毁信号量sem_destroy()函数原型// 销毁已初始化的无名信号量成功返回 0失败返回 -1 并设置 errno int sem_destroy(sem_t *sem);功能释放信号量占用的资源销毁后信号量不可再使用。关键注意必须确保所有线程都已不再使用该信号量无线程阻塞在 sem_wait() 上否则会导致未定义行为。仅能销毁 sem_init() 初始化的无名信号量不可销毁未初始化或已销毁的信号量。6获取信号量当前值sem_getvalue()// 获取信号量当前计数器值成功返回 0失败返回 -1 并设置 errno int sem_getvalue(sem_t *sem, int *sval);sval输出参数存储信号量当前值若 sem 0*sval 为当前计数器值。若 sem 0 且有线程阻塞等待*sval 通常为 0部分系统返回 -阻塞线程数。2.5.4.信号量的线程安全实操示例下面通过两个典型场景演示信号量的使用均保证线程安全场景 1二元信号量value1实现互斥等效互斥锁解决 i 线程不安全问题。场景 2计数信号量valueN实现生产者 - 消费者模型资源计数与线程协作。场景 1二元信号量实现互斥解决数据竞争#include stdio.h #include stdlib.h #include pthread.h #include semaphore.h #include unistd.h // 全局共享资源 int global_count 0; // 信号量二元信号量用于互斥 sem_t global_sem; // 线程入口函数执行 100000 次 i通过信号量保证原子性 void *thread_inc(void *arg) { for (int i 0; i 100000; i) { // 步骤 1获取信号量阻塞式原子减 1计数器从 1→0其他线程阻塞 if (sem_wait(global_sem) -1) { perror(sem_wait failed); pthread_exit(NULL); } // 临界区共享资源操作保证同一时间只有一个线程进入 global_count; // 步骤 2释放信号量原子加 1计数器从 0→1唤醒等待线程 if (sem_post(global_sem) -1) { perror(sem_post failed); pthread_exit(NULL); } } printf(【线程 %lu】执行完毕\n, (unsigned long)pthread_self()); return NULL; } int main() { pthread_t tid1, tid2; // 步骤 1初始化信号量二元信号量pshared0 线程共享value1 if (sem_init(global_sem, 0, 1) -1) { perror(sem_init failed); exit(EXIT_FAILURE); } // 步骤 2创建两个线程 if (pthread_create(tid1, NULL, thread_inc, NULL) ! 0) { perror(pthread_create tid1 failed); exit(EXIT_FAILURE); } if (pthread_create(tid2, NULL, thread_inc, NULL) ! 0) { perror(pthread_create tid2 failed); exit(EXIT_FAILURE); } // 步骤 3等待线程执行完毕 pthread_join(tid1, NULL); pthread_join(tid2, NULL); // 步骤 4销毁信号量 if (sem_destroy(global_sem) -1) { perror(sem_destroy failed); exit(EXIT_FAILURE); } // 预期结果200000线程安全无数据竞争 printf(最终 global_count%d\n, global_count); return 0; }编译与运行gcc sem_mutex.c -o sem_mutex -lpthread ./sem_mutex运行结果【线程 140708329981696】执行完毕 【线程 140708321588992】执行完毕 最终 global_count200000场景 2计数信号量实现生产者 - 消费者模型#include stdio.h #include stdlib.h #include pthread.h #include semaphore.h #include unistd.h #define BUFFER_SIZE 5 // 缓冲区大小资源总数 int buffer[BUFFER_SIZE]; int prod_index 0; // 生产者写入索引 int cons_index 0; // 消费者读取索引 // 信号量定义 sem_t sem_empty; // 空缓冲区数量初始BUFFER_SIZE资源计数 sem_t sem_full; // 满缓冲区数量初始0同步唤醒 sem_t sem_mutex; // 互斥信号量保护缓冲区索引操作二元信号量 // 生产者线程向缓冲区写入数据 void *producer(void *arg) { int data 1; while (1) { // 步骤 1获取空缓冲区计数减 1无空缓冲区则阻塞 sem_wait(sem_empty); // 步骤 2获取互斥锁保护缓冲区索引原子操作 sem_wait(sem_mutex); // 步骤 3写入缓冲区 buffer[prod_index] data; printf(【生产者】写入数据%d缓冲区索引%d\n,>编译与运行gcc sem_prod_cons.c -o sem_prod_cons -lpthread ./sem_prod_cons运行结果核心片段【生产者】写入数据1缓冲区索引0 【消费者】读取数据1缓冲区索引0 【生产者】写入数据2缓冲区索引1 【生产者】写入数据3缓冲区索引2 【消费者】读取数据2缓冲区索引12.5.5.信号量 vs 互斥锁核心区别与适用场景信号量和互斥锁都是线程安全的同步工具但设计目标和适用场景不同切勿混淆2.5.6.信号量使用的常见误区与最佳实践常见误区误区 1线程场景中 pshared 设为 1。「错误」线程间共享信号量应设为 01 是跨进程共享会增加开销且无需配合共享内存。误区 2释放未获取的信号量。「错误」会导致信号量计数器溢出逻辑混乱如生产者未写入数据却调用 sem_post(sem_full)。误区 3忽略信号量接口的返回值。「错误」sem_wait()/sem_post() 可能因信号中断、资源不足失败需检查返回值并处理。误区 4用互斥锁替代计数信号量。「错误」互斥锁无资源计数功能实现多资源协作需手动维护计数器复杂且易出错。误区 5信号量初始化后未销毁。「错误」会导致进程内资源泄露长期运行可能耗尽系统资源。最佳实践线程场景优先使用无名信号量轻量、高效接口简单无需关心内核资源管理。合理设置初始值互斥场景设为 1资源计数场景设为资源总数如缓冲区大小。最小化信号量的作用域仅在需要同步 / 互斥的代码块前后调用 sem_wait()/sem_post()避免不必要的阻塞。配对使用 sem_wait() 与 sem_post()确保每个获取操作都有对应的释放操作避免死锁。互斥与同步分离如生产者 - 消费者模型中用 sem_mutex 保护临界区用 sem_empty/sem_full 实现同步职责清晰。避免信号量嵌套多个信号量的获取顺序不一致可能导致死锁若需嵌套需统一获取顺序。2.5.7.核心总结信号量是线程安全的计数器 等待 / 唤醒机制支持互斥二元信号量和同步计数信号量比互斥锁更灵活。线程开发的核心是 POSIX 无名信号量sem_* 接口sem_init() 初始化时 pshared0编译需链接 -lpthread。核心接口sem_init()初始化、sem_wait()阻塞获取、sem_post()释放唤醒、sem_destroy()销毁操作均为原子性无数据竞争。二元信号量value1等效于互斥锁计数信号量valueN适用于多资源协作如生产者 - 消费者模型。最佳实践合理设置初始值、配对使用接口、检查返回值、及时销毁信号量避免逻辑混乱和资源泄露。信号量是多线程开发中实现复杂协作的核心工具尤其在高并发、有限资源场景中如连接池、缓冲区管理至关重要。3.其他线程安全保障手段除了上述核心同步互斥机制还有一些辅助手段可以提升线程安全性避免潜在问题。3.1.避免共享资源最小化共享这是最根本、最安全的线程安全方案尽量减少线程间的共享资源使用线程私有资源替代共享资源。线程私有数据TSD通过 pthread_key_create()、pthread_setspecific()、pthread_getspecific() 为每个线程创建独立的私有数据避免共享。局部变量线程内的函数局部变量存储在线程私有栈中不与其他线程共享天然线程安全。3.2.保证操作的原子性原子操作 API对于简单的数值操作如加减、赋值可以使用 Linux 提供的原子操作接口无需锁机制保证操作的原子性性能高于互斥锁。示例__sync_add_and_fetch(global_count, 1)原子实现 global_count__sync_sub_and_fetch(global_count, 1)原子实现 global_count--。适用场景简单的整数增减、比较交换等操作不支持复杂的逻辑操作。3.3.线程安全的库函数Linux 中的库函数分为「线程安全函数」和「线程不安全函数」线程安全函数内部实现了同步机制如互斥锁支持多线程并发调用如 printf()、pthread_* 系列函数。线程不安全函数无内部同步机制多线程调用可能导致数据混乱如 strtok()、gethostbyname()通常有对应的线程安全版本如 strtok_r()、gethostbyname_r()_r 表示可重入、线程安全。4.线程安全的常见误区与最佳实践4.1.常见误区误区 1加锁越多越安全。「错误」过多的锁会导致「锁竞争」加剧、性能下降还可能引发「死锁」多个线程互相等待对方释放锁。误区 2纯读操作一定线程安全。「错误」若有线程同时执行写操作纯读操作也会读取到不一致的数据可见性问题仍需同步机制。误区 3自旋锁性能一定优于互斥锁。「错误」自旋锁仅适用于临界区极短的场景临界区较长时互斥锁的性能更优。误区 4忽略死锁的预防。「错误」多个锁的获取顺序不一致、锁未释放、递归获取非递归锁都可能导致死锁一旦发生无法主动解除。4.2.最佳实践最小化锁的作用域锁仅包裹临界区代码共享资源操作避免锁包裹无关代码减少锁竞争时间。统一锁的获取顺序多个线程获取多把锁时遵循相同的顺序如先锁 A 后锁 B预防死锁。优先使用默认互斥锁无特殊性能要求时pthread_mutex_t 是最安全、最易用的选择避免过度优化。避免锁的嵌套尽量减少锁的嵌套使用嵌套过多容易导致死锁且难以排查。使用线程安全的库函数优先选择带 _r 后缀的可重入函数避免使用线程不安全的库函数。死锁的预防与排查避免持有锁时阻塞等待其他线程使用 pthread_mutex_trylock() 非阻塞获取锁排查死锁可使用 pstack、gdb 工具。5.核心总结线程不安全的根本原因是共享资源的并发访问、线程调度的不确定性、非原子操作的拆分。线程安全的核心保障是同步与互斥机制常用工具包括互斥锁通用、条件变量线程协作、自旋锁轻量级忙等待、读写锁多读少写优化。互斥锁是最常用的同步工具保证临界资源的排他性访问适用于大多数场景条件变量需与互斥锁配合实现线程间的高效协作。最佳实践最小化共享资源、最小化锁的作用域、统一锁的获取顺序、优先使用线程安全的接口避免死锁和性能问题。线程安全是多线程开发的必备技能直接决定程序的健壮性和稳定性尤其在后端服务、高并发程序开发中至关重要。

更多文章