2026/1/13 11:43:10
网站建设
项目流程
广告型网站,网站备案人授权书,公司网站建设会计你分录,哪些网站怎么进一、并发的本质1. 并发≠并行先弄清楚两个概念#xff1a;并发#xff08;Concurrency#xff09;#xff1a;多个任务在时间片上交替执行#xff0c;宏观上同时#xff0c;微观上是串行切换并行#xff08;Parallelism#xff09;#xff1a;多个任务在多…一、并发的本质1. 并发≠并行先弄清楚两个概念并发Concurrency多个任务在时间片上交替执行宏观上同时微观上是串行切换并行Parallelism多个任务在多个CPU核心上真正同时执行在单核ARM芯片上你写的多线程代码是并发在多核处理器上才可能是并行。但无论哪种情况只要存在共享资源就必须面对竞态条件Race Condition。2. 竞态的根源竞态的根源是多个执行流对共享资源的访问顺序不确定。解决它就是多线程编程的核心。访问顺序不确定有如下几种情况1一行C代码≠一条指令看这段常见的计数器代码counter看起来简单编译后实际是三条指令LOAD counter → 寄存器 ADD 寄存器 1 STORE 寄存器 → counter两个线程各执行10万次理论结果是20万。但实测往往只有13-18万。原因就是两个线程的指令可能交叉执行。2编译器和CPU乱序执行为了优化性能编译器可能重排指令顺序CPU也有乱序执行机制。我们写的代码顺序不一定是实际执行顺序。3多核CPU各有各的缓存每个核心都有自己的L1/L2 Cache对同一内存地址的修改不会立即对其他核心可见。这叫缓存一致性问题。二、POSIX线程库三大同步原语POSIX线程库提供了几种同步机制互斥锁、条件变量、读写锁。2.1 互斥锁互斥锁的语义很简单同一时刻只有一个线程能持有锁。需要特别注意的是锁的粒度要小。如果误把整个业务逻辑都放在锁里面结果多线程变成了排队执行性能还不如单线程。错误示范锁的粒度太大的例子正确做法只锁共享数据的访问不锁计算逻辑。2.2 条件变量生产者线程产生数据消费者线程处理数据。消费者怎么知道有数据了这时候可以使用条件变量。条件变量正是解决线程等待-通知场景的最优解——它能让线程在条件不满足时休眠条件满足时精准唤醒既保证响应速度又能最大化降低CPU占用。错误方案轮询方式正确方案使用条件变量使用条件变量的典型步骤等待方步骤加互斥锁pthread_mutex_lock循环检查条件while(condition false)条件不满足时调用pthread_cond_wait休眠被唤醒后重新检查条件执行业务逻辑解锁互斥锁pthread_mutex_unlock。通知方步骤加互斥锁pthread_mutex_lock修改条件如设置flag为true、添加数据到队列发送通知pthread_cond_signal或pthread_cond_broadcast解锁互斥锁pthread_mutex_unlock。代码如为什么必须用while而不是if因为存在虚假唤醒spurious wakeup——线程可能在没有收到signal的情况下被唤醒。这是POSIX标准允许的行为用while可以再次检查条件。2.3 读写锁如果你的场景是90%读、10%写用互斥锁太浪费——读操作之间本不需要互斥。下图对比三种同步原语的适用场景三、死锁比方说两个线程甚至多个线程需要获取两个临界值也就是需要两个锁死锁是多线程编程最经典的问题。它的四个必要条件Coffman条件互斥资源不能共享持有并等待持有一个锁的同时等待另一个锁不可抢占锁不能被强制释放循环等待A等BB等A破坏任意一个条件就能预防死锁。实践中最有效的是破坏循环等待规定加锁顺序。经典AB-BA死锁例子如以上程序卡死CPU占用为0。这是经典的AB-BA死锁模式。两个线程以相反的顺序获取两把锁在特定时序下互相等待形成死锁。执行时序图为什么发生死锁呢线程1和线程2同时启动线程1获取了锁A又想要获取锁B此时线程2获取了锁B又想要获取锁A此时两个线程都不能获取第二个锁就形成了死锁如何解决呢两个线程获取锁的顺序一定要一致比如一开始都去获取锁A当一个线程获取到了锁A另外一个线程就会进入等待此时不会形成死锁修复以上死锁问题统一加锁顺序释放锁的时候都是先B后A规避死锁问题的工程实践建议在代码规范中明确锁的层级顺序使用pthread_mutex_trylock实现超时机制开发阶段启用死锁检测工具如Helgrind、ThreadSanitizer四、总结三条核心原则最小化共享能不共享就不共享能用消息传递就不用共享内存最小化临界区锁的粒度越小越好只保护数据访问不保护计算逻辑统一加锁顺序从根源上避免死锁