十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux--条件变量、信号量、读写锁原理与实战

Linux--条件变量、信号量、读写锁原理与实战 前言多线程开发中竞态条件、线程阻塞、数据错乱是最常见的难题。为了让线程有序、安全访问共享资源Linux POSIX 提供了三类主流同步工具条件变量、POSIX无名信号量、读写锁。本文围绕「如何合理选择同步工具、写出稳定无BUG的多线程代码」展开从原理、API、易错点、实战案例生产者消费者、读者写者逐层讲解搭配通俗举例与可运行C代码夯实Linux并发基础。一、基础概念铺垫1.1 同步、竞态条件、饥饿同步在保证共享数据安全的前提下约束线程执行顺序避免线程长期拿不到资源饥饿卡死。竞态条件多线程时序混乱并发读写共享数据最终程序结果异常是线程不安全的根源。举例两个线程同时执行 i 指令被拆分穿插执行数值被覆盖变少。饥饿某一线程长期无法获取锁、CPU资源一直阻塞无法运行。举例大量读者持续占用读锁写者一直拿不到写锁永久等待就是写者饥饿。1.2 条件变量基础定义条件变量本身不是锁必须和互斥锁配合使用。作用线程条件不满足时主动让出CPU进入休眠等待其他线程修改条件后发送信号唤醒等待线程。生活化例子食堂打饭饭没做好条件不满足学生排队等待厨师做好饭菜修改条件喊学生来打饭发送信号唤醒。二、条件变量核心详解2.1 全套POSIX核心API函数作用关键注意事项pthread_cond_init动态初始化条件变量动态初始化必须destroy销毁静态初始化PTHREAD_COND_INITIALIZER无需销毁pthread_cond_destroy销毁条件变量禁止销毁正在被线程等待的condpthread_cond_wait阻塞等待原子操作释放锁→阻塞休眠唤醒后自动重新加锁必须while包裹pthread_cond_timedwait限时等待绝对时间超时自动返回防止永久卡死pthread_cond_signal唤醒单个线程无等待线程时信号丢失pthread_cond_broadcast唤醒全部线程引发惊群效应配合while规避逻辑错误重点pthread_cond_wait 原子三动作原子释放绑定的互斥锁让其他线程修改共享条件线程阻塞休眠等待唤醒被唤醒后自动重新抢占互斥锁函数返回。标准使用范式必背// 等待线程消费者模板pthread_mutex_lock(mutex);// while 循环解决虚假唤醒、惊群效应while(条件不成立){pthread_cond_wait(cond,mutex);}// 安全操作共享资源pthread_mutex_unlock(mutex);// 唤醒线程生产者模板pthread_mutex_lock(mutex);修改共享条件;pthread_cond_signal(cond);pthread_mutex_unlock(mutex);2.2 两个高频易错点易错1为什么必须搭配互斥锁如果解锁、等待分开执行会出现信号丢失线程A判断条件不满足、解锁后还没进入等待线程B快速修改条件并发送信号A错过信号永久阻塞。pthread_cond_wait 将「解锁等待」合并为原子操作彻底杜绝该问题。易错2为什么必须用while不能用if虚假唤醒系统偶尔会无信号自动唤醒线程if只会判断一次会直接错误执行逻辑惊群效应broadcast唤醒所有线程多个线程抢锁一个线程执行完修改条件其余线程条件再次失效while会二次校验。2.3 简单演示案例单线程等待唤醒#includestdio.h#includeunistd.h#includepthread.hpthread_cond_tcondPTHREAD_COND_INITIALIZER;pthread_mutex_tmutexPTHREAD_MUTEX_INITIALIZER;void*wait_thread(void*arg){while(1){pthread_mutex_lock(mutex);printf(等待唤醒信号\n);pthread_cond_wait(cond,mutex);printf(成功被唤醒\n);pthread_mutex_unlock(mutex);}}void*signal_thread(void*arg){while(1){sleep(2);pthread_mutex_lock(mutex);pthread_cond_signal(cond);printf(发送唤醒信号\n);pthread_mutex_unlock(mutex);}}intmain(){pthread_tt1,t2;pthread_create(t1,NULL,wait_thread,NULL);pthread_create(t2,NULL,signal_thread,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);return0;}编译运行gcc test.c -o cond -lpthread每2秒唤醒一次等待线程。三、生产者消费者模型两大主流实现生产者消费者是同步最经典场景缓冲区解耦生产者、消费者生产满阻塞生产者消费空阻塞消费者。方案一互斥锁双条件变量实现设计1把互斥锁保护缓冲区not_empty 唤醒消费者、not_full 唤醒生产者环形缓冲区牺牲一格区分空/满。完整代码、编译方式前文已提供核心要点inout缓冲区为空 (in1)%SIZEout 缓冲区已满所有等待均用while循环生产完成唤醒消费者消费完成唤醒生产者。方案二POSIX无名信号量实现3.1 信号量基础信号量是非负计数器自带计数不会丢失通知分为无名信号量同进程多线程同步日常开发常用pshared0有名信号量跨进程同步。核心P/V操作sem_wait(P)计数-1值为0则阻塞sem_post(V)计数1唤醒等待线程。3.2 生产者消费者信号量设计三个信号量分工empty空位数量生产者等待full数据数量消费者等待mutex二元互斥信号量保护缓冲区并发读写。执行顺序先等待资源计数再加互斥锁防止死锁。3.3 条件变量VS信号量对比对比项条件变量互斥锁POSIX信号量计数无计数器信号易丢失自带计数器通知永久保存锁依赖强制绑定互斥锁自带同步共享资源仍需互斥定位事件等待、异步通知资源数量限流、计数同步适用场景线程池、复杂事件调度连接池、固定容量缓冲区工程选型业务逻辑复杂优先条件变量单纯资源限流优先信号量。四、读写锁与读者写者问题4.1 读写锁特性读写锁是优化版互斥锁适配读多写少场景无锁多个读者可同时加读锁并发读取有读者在读写者阻塞写者持有锁所有读者、写者全部阻塞。常用API阻塞pthread_rwlock_rdlock 读锁、 pthread_rwlock_wrlock 写锁、unlock 解锁非阻塞 tryrdlock/trywrlock 获取失败立刻返回EBUSY不会卡死线程。4.2 读者写者三大策略读者优先Linux默认读者持续进入写者饥饿读多写少场景性能最优写者优先写者优先抢占锁保证数据新鲜读者易饥饿公平策略按申请顺序排队实现复杂极少使用。4.3 实战案例读者优先读写锁多读者并发读取写者独占修改前文代码运行可直观观察读者大批量同时打印写者单独独占执行。4.4 读写锁VS互斥锁互斥锁任何时刻仅一个线程执行读写均衡、写密集场景使用读写锁读多写少场景大幅提升并发性能。五、Linux主流同步工具汇总同步工具核心特点典型落地场景互斥锁mutex同一时刻仅一个线程进入临界区普通变量互斥保护、写密集并发条件变量cond事件等待唤醒无计数器信号易丢失线程池阻塞队列、异步事件POSIX信号量自带计数P/V操作通知不丢失资源池、固定容量缓冲区限流读写锁rwlock读者并发、写者独占默认读者优先缓存、配置文件、读多写少业务六、全文核心总结线程不安全根源是竞态条件同步工具本质就是约束线程时序、隔离临界区条件变量必须搭配mutex、while循环、原子wait操作规避信号丢失、虚假唤醒生产者消费者两种实现条件变量灵活通用信号量计数稳定读写锁专为读多写少优化读者优先为系统默认需警惕写者饥饿选型口诀普通互斥用mutex等待唤醒用cond数量限选用信号量多读少写用读写锁。
返回列表