多核并行编程的背景在摩尔定律失效之前,通过提高主频、硬件超线程等技术提升处理器性能可以满足应用需求。随着主频的提升慢慢逼近光速壁,摩尔定律开始逐渐失效,多核集成成为处理器性能提升的主流手段。现在市场上很难见到单核处理器,就是这种发展趋势的明证。为了充分发挥多核计算资源的优势,多核下的并行编程是必然的。Linux内核是典型的多核并行编程场景。但是多核下的并行编程有很多挑战。多核并行编程的挑战目前主流的计算机都是冯·诺依曼架构,即共享内存的计算模型。这种进程计算模型对并行计算并不友好。下图是一个典型的计算机硬件架构。在该架构中,具有以下设计特点:多个CPU内核提高了处理器的计算和处理能力;多级缓存提高了CPU访问主存的效率;每个CPU都有本地内存(NUMA(non-uniformmemoryaccess)),进一步提高CPU访问主内存的效率;storebuffer模块改善了缓存写响应延迟导致的写暂停问题;无效队列模块提高了无效响应的延迟,在无效命令被放入队列后立即发送响应;外设DMA支持直接访问主存,提高CPU使用效率;这些硬件系统设计特点也引入了很多问题,最大的问题是缓存一致性和乱序执行。缓存一致性问题由缓存一致性协议MESI解决,由硬件保证,对软件透明。MESI协议保证所有CPU在单个缓存行中修改单个变量的顺序是一致的,但不保证不同变量的修改在所有CPU上看到的顺序相同。这会造成混乱。不仅如此,乱序的原因还有很多:storebuffer导致的延迟处理会导致乱序;invalidatequeue造成的延迟处理会造成乱序;编译优化会造成乱序;分支预测、多流水线等CPU硬件优化技术会造成乱序;外设DMA会造成数据乱序;在这种情况下,连简单的++操作的原子性都无法保证。这些问题必须通过多核并行编程的新技术手段来解决。多核并行编程的关键技术锁技术Linux内核提供了多种锁机制,如自旋锁、信号量、互斥锁、读写锁、顺序锁等。各种锁的简单比较如下。具体的实现和使用细节这里就不展开了。可以参考《Linux内核设计与实现》等书籍的相关章节。自旋锁,无睡眠,无进程上下文切换开销,可用于中断上下文和临界区较小的场合;信号量,会休眠,支持多个并发体同时进入临界区,可用于可能休眠或长临界区;互斥量,类似于信号量,但只支持一个并发体同时进入临界区;读写锁,支持读写并发,读写互斥,读会延迟写,对读友好,适用于读为主的场合;顺序锁,支持读并发,write和read/write互斥,write会延迟read,写友好,适合写为主的场合;锁技术虽然可以有效提供并行执行下的竞争保护,但是锁的并行扩展性很差,无法充分发挥多核的性能优势。锁的粒度太粗,扩展性受限,粒度太细,会导致系统开销巨大,设计难度大,容易造成死锁。除了并发扩展性差和死锁之外,锁还会带来很多其他问题,比如锁拥挤、活锁、饥饿、非公平锁、优先级反转等。但是,有一些技术手段或指南可以解决或减轻风险这些问题。统一顺序使用锁(锁级别)解决死锁问题;解决活锁/饥饿问题的指数退避;范围锁(树锁),解决锁惊吓问题;优先级继承解决优先级倒置问题;原子技术原子技术主要是解决缓存不一致和乱序执行破坏原子访问的问题。主要的原子原语有:ACCESS_ONECE():只限制编译器对内存访问的优化;barrier():只限制编译器的乱序优化;smb_wmb():写入内存屏障,刷新storebuffer,同时限制编译和CPU乱序优化;smb_rmb():读取内存屏障,刷新invalidate队列,同时限制编译器和CPU的乱序优化;smb_mb():读写内存屏障,同时刷新storebuffer和invalidatequeue,同时限制编译处理器和CPU的乱序优化;atomic_inc()/atomic_read()等:整型原子操作;还有一点要提的是atomic_inc()原语为了保证原子性需要刷新缓存,而缓存行在多核系统下传播是相当耗时的,其在多核下的并行扩展性很穷。无锁技术上一节提到的原子技术就是无锁技术中的一种。此外,无锁技术还包括RCU、Hazardpointer等。值得一提的是,这些无锁技术都是基于内存屏障实现的。危险指针主要用于对象生命周期管理,类似于引用计数,但比引用计数具有更好的并行扩展性;RCU适用场景很多,可以替代:读写锁、引用计数、垃圾收集器、WaitingThingsend等,具有更好的并行扩展性。但是RCU也有一些不适用的场景,比如写强调;临界区长度;临界区睡眠等场景。但是,所有的无锁原语只能解决读端的并行可扩展性问题,而写端的并行可扩展性只能通过数据分段技术来解决。数据分区技术对数据结构进行分区,减少共享数据,是并行可扩展性的根本解决方案。对切??分友好(即并行友好)的数据结构有:数据分割。可扩展性。除了使用合适的数据结构,合理的切分准则也很重要:读写切分:将面向读的数据和面向写的数据分开;路径切分:按照独立的代码执行路径划分数据;specialitemsSegmentation:将频繁更新的数据绑定到指定的CPU/线程;Segmentationofownership:按照CPU/线程数对数据结构进行切分,将数据划分为per-cpu/per-thread;在4种分割规则中,所有权分割是最彻底的分割。以上多核并行编程内容基本涵盖了Linux内核中并发编程的所有关键技术。当然,并行编程中还有很多其他技术没有应用到Linux内核中,比如无副作用的并行函数式编程技术(Erlang/Go等)、消息传递、MapReduce等。
