引言在前面的文章中我们学习了线程的创建、退出、等待以及使用互斥锁和信号量解决线程同步问题。今天我们将继续深入探讨多线程编程的另外两个重要同步机制读写锁和条件变量以及多线程环境下的函数安全问题。第一部分线程同步方法回顾Linux系统提供的线程同步方法主要有四种同步方法适用场景特点互斥锁保护临界区同一时刻只有一个线程能持有锁信号量资源计数、同步计数器可控制多个资源读写锁读多写少场景读锁共享写锁独占条件变量等待条件满足配合互斥锁使用线程间通知机制第二部分读写锁Read-Write Lock一、读写锁的概念读写锁与互斥锁的区别在于互斥锁加锁后其他线程无法再加锁而读写锁在读多写少场景下允许多个读操作同时进行但写操作必须互斥。读写锁的适用场景多个线程仅需读取共享数据不修改时允许并发读操作涉及写操作时必须独占访问避免数据冲突二、读写锁的核心规则操作组合是否兼容说明读锁 读锁✅ 兼容多个线程可同时持有读锁读锁 写锁❌ 互斥持有写锁时禁止其他线程加读锁或写锁写锁 写锁❌ 互斥同一时间仅允许一个写锁存在三、读写锁的接口#include pthread.h // 初始化读写锁 int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); // 加读锁阻塞 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 加写锁阻塞 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 解锁 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 销毁读写锁 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); // 非阻塞版本 int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);四、读写锁示例#include stdio.h #include stdlib.h #include pthread.h #include unistd.h pthread_rwlock_t rwlock; int shared_data 100; // 读线程函数 void* reader(void* arg) { int id *(int*)arg; pthread_rwlock_rdlock(rwlock); printf(读线程%d: 开始读取数据...\n, id); sleep(1); printf(读线程%d: 读取到数据 %d\n, id, shared_data); printf(读线程%d: 读结束\n, id); pthread_rwlock_unlock(rwlock); return NULL; } // 写线程函数 void* writer(void* arg) { int id *(int*)arg; pthread_rwlock_wrlock(rwlock); printf(写线程%d: 开始写入数据...\n, id); shared_data rand() % 1000; sleep(2); printf(写线程%d: 写入数据 %d\n, id, shared_data); printf(写线程%d: 写结束\n, id); pthread_rwlock_unlock(rwlock); return NULL; } int main() { pthread_t readers[3], writer_tid; int ids[3] {1, 2, 3}; int wid 1; // 初始化读写锁 pthread_rwlock_init(rwlock, NULL); // 创建3个读线程 for (int i 0; i 3; i) { pthread_create(readers[i], NULL, reader, ids[i]); } // 创建1个写线程 pthread_create(writer_tid, NULL, writer, wid); // 等待所有线程结束 for (int i 0; i 3; i) { pthread_join(readers[i], NULL); } pthread_join(writer_tid, NULL); // 销毁读写锁 pthread_rwlock_destroy(rwlock); return 0; }运行结果特点无锁时读写操作交叉执行存在数据竞争风险加锁后读操作可并行写操作严格串行且读写操作互不干扰第三部分条件变量Condition Variable一、条件变量的概念条件变量是多线程编程中较抽象的概念。根据《高性能服务器编程》条件变量用于线程间同步共享数据的值其本质是提供线程间通知机制。当特定条件满足时通过接口通知等待线程执行任务。核心操作作用pthread_cond_wait将线程加入等待队列并阻塞pthread_cond_signal唤醒等待队列中的一个线程pthread_cond_broadcast唤醒等待队列中的所有线程二、条件变量接口#include pthread.h // 初始化条件变量 int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); // 等待条件必须与互斥锁配合 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); // 限时等待 int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); // 唤醒一个等待线程 int pthread_cond_signal(pthread_cond_t *cond); // 唤醒所有等待线程 int pthread_cond_broadcast(pthread_cond_t *cond); // 销毁条件变量 int pthread_cond_destroy(pthread_cond_t *cond);三、为什么pthread_cond_wait必须配合互斥锁pthread_cond_wait必须与互斥锁配合使用原因如下原子性保护wait操作包含将线程加入等待队列和释放锁两个步骤需确保不可分割唤醒安全被唤醒时线程会重新加锁避免与其他线程操作冲突执行流程四、条件变量示例控制线程输出顺序#include stdio.h #include stdlib.h #include pthread.h #include unistd.h pthread_cond_t cond; pthread_mutex_t mutex; char buffer[256]; int done 0; void* thread_func(void* arg) { char* name (char*)arg; while (1) { pthread_mutex_lock(mutex); pthread_cond_wait(cond, mutex); if (done) { pthread_mutex_unlock(mutex); break; } printf(%s 读取到数据: %s\n, name, buffer); pthread_mutex_unlock(mutex); } return NULL; } int main() { pthread_t t1, t2; pthread_cond_init(cond, NULL); pthread_mutex_init(mutex, NULL); pthread_create(t1, NULL, thread_func, 线程A); pthread_create(t2, NULL, thread_func, 线程B); while (1) { printf(请输入数据: ); fgets(buffer, sizeof(buffer), stdin); // 去除换行符 buffer[strlen(buffer) - 1] \0; pthread_mutex_lock(mutex); if (strcmp(buffer, end) 0) { done 1; pthread_cond_broadcast(cond); } else { pthread_cond_signal(cond); } pthread_mutex_unlock(mutex); if (done) break; } pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_cond_destroy(cond); pthread_mutex_destroy(mutex); return 0; }关键点说明所有唤醒操作需先加锁避免与wait的队列操作冲突pthread_cond_signal按需唤醒单个线程pthread_cond_broadcast用于唤醒所有等待线程唤醒策略应根据业务需求选择第四部分线程安全函数一、strtok函数的多线程问题strtok函数用于字符串分割通过分隔符将字符串拆分为多个字段。但它在多线程环境中存在严重问题。问题原因strtok函数内部使用静态变量记录分割位置多线程并发调用时共享同一指针引发数据覆盖问题示例// 错误示例多线程调用strtok void* thread_func1(void* arg) { char str[] abcde; char* token strtok(str, ); // ... } void* thread_func2(void* arg) { char str[] 12345; char* token strtok(str, ); // ... } // 输出结果混杂如a1b2c3二、线程安全版本strtok_r解决方案使用线程安全版本strtok_r通过额外参数指针地址独立记录分割位置。// 线程安全版本 char* strtok_r(char *str, const char *delim, char **saveptr); // 使用示例 void* thread_func(void* arg) { char buffer[] hello world from thread; char* saveptr; char* token strtok_r(buffer, , saveptr); while (token ! NULL) { printf(%s\n, token); token strtok_r(NULL, , saveptr); } return NULL; }三、线程安全函数设计原则原则说明避免静态/全局变量或通过参数传递独立存储空间识别线程安全版本如strtok_r后缀_r通常表示线程安全版本使用同步机制若必须共享状态使用锁保护系统库函数线程安全标识后缀_rreentrant通常表示线程安全版本如rand_r、localtime_r、strtok_r等第五部分信号量控制线程输出顺序一、经典问题三个线程交替打印ABC需求三个线程分别输出字母A、B、C要求严格按ABC顺序循环打印。输出结果A B C A B C A B C ...二、信号量解决方案#include stdio.h #include pthread.h #include semaphore.h #define LOOP_COUNT 10 sem_t sem_a, sem_b, sem_c; void* print_a(void* arg) { for (int i 0; i LOOP_COUNT; i) { sem_wait(sem_a); printf(A ); fflush(stdout); sem_post(sem_b); } return NULL; } void* print_b(void* arg) { for (int i 0; i LOOP_COUNT; i) { sem_wait(sem_b); printf(B ); fflush(stdout); sem_post(sem_c); } return NULL; } void* print_c(void* arg) { for (int i 0; i LOOP_COUNT; i) { sem_wait(sem_c); printf(C ); fflush(stdout); sem_post(sem_a); } return NULL; } int main() { pthread_t t1, t2, t3; // 初始化信号量sema1, semb0, semc0 sem_init(sem_a, 0, 1); sem_init(sem_b, 0, 0); sem_init(sem_c, 0, 0); pthread_create(t1, NULL, print_a, NULL); pthread_create(t2, NULL, print_b, NULL); pthread_create(t3, NULL, print_c, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); sem_destroy(sem_a); sem_destroy(sem_b); sem_destroy(sem_c); return 0; }同步逻辑线程操作效果线程AP(sem_a) → 打印A → V(sem_b)初始sem_a1先执行线程BP(sem_b) → 打印B → V(sem_c)sem_b初始0等待A唤醒线程CP(sem_c) → 打印C → V(sem_a)sem_c初始0等待B唤醒关键点初始值设置确保线程A优先执行后续通过信号量链式触发避免线程竞争导致乱序。总结一、四种线程同步机制对比机制适用场景核心特点互斥锁保护临界区独占访问信号量资源计数可控制多个资源读写锁读多写少读共享写独占条件变量等待条件满足通知机制需配合互斥锁二、读写锁核心规则规则说明读锁与读锁兼容可并发读锁与写锁互斥写锁与写锁互斥三、条件变量使用规范规则说明必须配合互斥锁wait前必须加锁wait内部会解锁wait返回时已重新加锁被唤醒后自动重新获取锁signal/broadcast前建议加锁保证等待队列状态稳定四、线程安全函数非安全函数安全版本区别strtokstrtok_r增加saveptr参数randrand_r增加种子参数localtimelocaltime_r结果存入党参本文介绍了多线程编程的另外两个重要同步机制读写锁读多写少场景下提供更好的并发性能条件变量实现线程间的通知机制配合互斥锁使用线程安全函数如何识别和使用线程安全版本的库函数信号量控制输出顺序经典的ABC交替打印问题面试高频考点读写锁与互斥锁的区别及适用场景条件变量必须配合互斥锁的原因strtok与strtok_r的区别使用信号量控制线程执行顺序的方法学习建议理解读写锁的兼容规则动手修改代码观察效果掌握条件变量的标准使用模式加锁→wait→检查条件→解锁编写ABC交替打印代码加深对信号量同步的理解注意区分线程安全和非线程安全函数避免踩坑