Linux多核并行编程关键技术
多核并行编程的背景
在摩尔定律失效之前,提升处理器性能通过主频提升、硬件超线程等技术就能满足应用需要。随着主频提升慢慢接近撞上光速这道墙,摩尔定律开始逐渐失效,多核集成为处理器性能提升的主流手段。现在市面上已经很难看到单核的处理器,就是这一发展趋势的佐证。要充分发挥多核丰富的计算资源优势,多核下的并行编程就不可避免,Linux kernel就是一典型的多核并行编程场景。但多核下的并行编程却挑战多多。
多核并行编程的挑战
目前主流的计算机都是冯诺依曼架构,即共享内存的计算模型,这种过程计算模型对并行计算并不友好。下图是一种典型的计算机硬件体系架构。

这种架构中,有如下设计特点:
- 多个CPU核改善处理器的计算处理能力;
- 多级cache改善CPU访问主存的效率;
- 各个CPU都有本地内存(NUMA(非一致性内存访问)),进一步改善CPU访问主存的效率;
- store buffer模块改善cache write由于应答延迟而造成的写停顿问题;
- invalidate queue模块改善使无效应答的时延,把使无效命令放入queue后就立即发送应答;
- 外设DMA支持直接访问主存,改善CPU使用效率;
这些硬件体系设计特点也引入很多问题,最大的问题就是cache一致性问题和乱序执行问题。
cache一致性问题由cache一致性协议MESI解决,MESI由硬件保证,对软件来说是透明的。MESI协议保证所有CPU对单个cache line中单个变量修改的顺序保持一致,但不保证不同变量的修改在所有CPU上看到的是相同顺序。这就造成了乱序。不仅如此,乱序的原因还有很多:
- store buffer引起的延迟处理,会造成乱序;
- invalidate queue引起的延迟处理,会造成乱序;
- 编译优化,会造成乱序;
- 分支预测、多流水线等CPU硬件优化技术,会造成乱序;
- 外设DMA,会造成数据乱序;
这种情况造成,就连简单的++运算操作的原子性都无法保证。这些问题必须采用多核并行编程新的技术手段来解决。
多核并行编程关键技术
锁技术
Linux kernel提供了多种锁机制,如自旋锁、信号量、互斥量、读写锁、顺序锁等。各种锁的简单比较如下,具体实现和使用细节这里就不展开了,可以参考《Linux内核设计与实现》等书的相关章节。
- 自旋锁,不休眠,无进程上下文切换开销,可以用在中断上下文和临界区小的场合;
- 信号量,会休眠,支持同时多个并发体进入临界区,可以用在可能休眠或者长的临界区的场合;
- 互斥量,类似与信号量,但只支持同时只有一个并发体进入临界区;
- 读写锁,支持读并发,写写/读写间互斥,读会延迟写,对读友好,适用读侧重场合;
- 顺序锁,支持读并发,写写/读写间互斥,写会延迟读,对写友好,适用写侧重场合;
锁技术虽然能有效地提供并行执行下的竞态保护,但锁的并行可扩展性很差,无法充分发挥多核的性能优势。锁的粒度太粗会限制扩展性,粒度太细会导致巨大的系统开销,而且设计难度大,容易造成死锁。除了并发可扩展性差和死锁外,锁还会引入很多其他问题,如锁惊群、活锁、饥饿、不公平锁、优先级反转等。不过也有一些技术手段或指导原则能解决或减轻这些问题的风险。
- 按统一的顺序使用锁(锁的层次),解决死锁问题;
- 指数后退,解决活锁/饥饿问题;
- 范围锁(树状锁),解决锁惊群问题;
- 优先级继承,解决优先级反转问题 ;
原子技术
原子技术主要是解决cache和内存不一致性和乱序执行对原子访问的破坏问题。Linux kernel中主要的原子原语有:
- ACCESS_ONCE()、READ_ONCE() and WRITE_ONCE():禁止编译器对数据访问的优化,强制从内存而不是缓存中获取数据;
- barrier():乱序访问内存屏障,限制编译器的乱序优化;
- smb_wmb():写内存屏障,刷新store buffer,同时限制编译器和CPU的乱序优化;
- smb_rmb():读内存屏障,刷新invalidate queue,同时限制编译器和CPU的乱序优化;
- smb_mb():读写内存屏障,同时刷新store buffer和invalidate queue,同时限制编译器和CPU的乱序优化;
- atomic_inc()/atomic_read()等:整型原子操作;
严格来说,Linux kernel作为系统软件,实现受硬件影响很大,不同硬件有不同的内存模型,因此,不同于高级语言,Linux kernel的原子原语语义并没有一个统一模型。比如在SMP的ARM64 CPU上,barrier、smb_wmb、smb_rmb的实现与smb_mb都是一样的,都是volatile ("" ::: "memory")。
另外,再多提一句的是,atomic_inc()原语为了保证原子性,需要对cache进行刷新,而缓存行在多核体系下传播相当耗时,其多核下的并行可扩展性差。
无锁技术
上一小节中所提到的原子技术,是无锁技术中的一种,除此之外,无锁技术还包括RCU、Hazard pointer等。值得一提的是,这些无锁技术都基于内存屏障实现的。
- Hazard pointer主要用于对象的生命周期管理,类似引用计数,但比引用计数有更好的并行可扩展性;
- RCU适用的场景很多,其可以替代:读写锁、引用计数、垃圾回收器、等待事物结束等,而且有更好的并行扩展性。但RCU也有一些不适用的场景,如写侧重;临界区长;临界区内休眠等场景。
不过,所有的无锁原语也只能解决读端的并行可扩展性问题,写端的并行可扩展性只能通过数据分割技术来解决。
数据分割技术
分割数据结构,减少共享数据,是解决并行可扩展性的根本办法。对分割友好(即并行友好)的数据结构有:
- 数组
- 哈希表
- 基树(Radix Tree)/稀疏数组
- 跳跃列表(skip list)
使用这些便于分割的数据结构,有利于我们通过数据分割来改善并行可扩展性。
除了使用合适的数据结构外,合理的分割指导规则也很重要:
- 读写分割:以读为主的数据与以写为主的数据分开;
- 路径分割:按独立的代码执行路径来分割数据;
- 专项分割:把经常更新的数据绑定到指定的CPU/线程中;
- 所有权分割:按CPU/线程个数对数据结构进行分割,把数据分割到per-cpu/per-thread中;
4种分割规则中,所有权分割是分割最彻底的。
以上这些多核并行编程内容基本上涵盖了Linux kernel中所有的并发编程关键技术。当然并行编程还有很多其他技术没有应用到Linux kernel中的,如无副作用的并行函数式编程技术(Erlang/Go等)、消息传递、MapReduce等等。
Linux多核并行编程关键技术的更多相关文章
- .NET 4 并行(多核)编程系列之一入门介绍
.NET 4 并行(多核)编程系列之一入门介绍 本系列文章将会对.NET 4中的并行编程技术(也称之为多核编程技术)以及应用作全面的介绍. 本篇文章的议题如下: 1. 并行编程和多线程编程的区别. ...
- 使用C#和.NET 4编写的并行应用程序“多核并发编程的规则”
“多核并发编程的规则” 规则的描述如下 1. 并发编程的思想—这条规则就是要谨记并发编程思想进行设计,就像前边章节所提交的. 2. 面向抽象编程-你可以利用.NET4中的TPL提供 ...
- Go 开发关键技术指南 | Go 面向失败编程 (内含超全知识大图)
作者 | 杨成立(忘篱) 阿里巴巴高级技术专家 关注"阿里巴巴云原生"公众号,回复 Go 即可查看清晰知识大图! 导读:从问题本身出发,不局限于 Go 语言,探讨服务器中常常遇到的 ...
- .NET 4 并行(多核)编程系列之四 Task的休眠
原文:.NET 4 并行(多核)编程系列之四 Task的休眠 .NET 4 并行(多核)编程系列之四 Task的休眠 前言:之前的几篇文章断断续续的介绍了Task的一些功能:创建,取消.本篇介绍Tas ...
- .NET 4 并行(多核)编程系列之三 从Task的取消
原文:.NET 4 并行(多核)编程系列之三 从Task的取消 .NET 4 并行(多核)编程系列之三 从Task的取消 前言:因为Task是.NET 4并行编程最为核心的一个类,也我们在是在并行编程 ...
- .NET 4 并行(多核)编程系列之二 从Task开始
原文:.NET 4 并行(多核)编程系列之二 从Task开始 .NET 4 并行(多核)编程系列之二 从Task开始 前言:我们一步步的从简单的开始讲述,还是沿用我一直的方式:慢慢演化,步步为营. ...
- [转].NET 4 并行(多核)编程系列之二 从Task开始
本文转自:http://www.cnblogs.com/yanyangtian/archive/2010/05/22/1741379.html .NET 4 并行(多核)编程系列之二 从Task开始 ...
- 并行编程(Parallel Framework)
前言 并行编程:通过编码方式利用多核或多处理器称为并行编程,多线程概念的一个子集. 并行处理:把正在执行的大量的任务分割成小块,分配给多个同时运行的线程.多线程的一种. 并行编程分为如下几个结构: 1 ...
- OpenCL学习笔记(二):并行编程概念理解
欢迎转载,转载请注明:本文出自Bin的专栏blog.csdn.net/xbinworld. 技术交流QQ群:433250724,欢迎对算法.技术.应用感兴趣的同学加入. 并行编程的需求是显而易见的,其 ...
随机推荐
- Html.ActionLink与Url.Action区别
一.@Html.ActionLink()概述 在MVC的Rasor视图引擎中,微软采用一种全新的方式来表示从前的超链接方式,它代替了从前的繁杂的超链接标签,让代码看起来更加简洁.通过浏览器依然会解析成 ...
- Contest2178 - 2019-4-18 高一noip基础知识点 测试7 题解版
刚刚改完题,才有时间发题解 传送门 T1 exgcd裸题 对a,b跑exgcd,答案就是x*c/gcd(a,b),y*c/gcd(a,b) 不合法的情况:当且仅当c%gcd(a,b)!=0 代码 T2 ...
- grep sed awk 3个Linux中对文件内容操作的命令
在学习Linux命令中,发现3个有关于文件内容操作的命令grep,sed和awk,在这里简单汇总这3个命令主要作用,在实际中找到最合适的情景应用,详细用法可以参考其他文章. 1.grep命令 主要作用 ...
- SqlServer查询某数据在某表某列中
create proc spFind_Column_In_DB ( @type int,--类型:为文字类型.为数值类型 @str nvarchar(100)--需要搜索的名字 ) as --创建临时 ...
- OnePlus5刷机后一直检查更新
大概是由于爱折腾,上一个手机是Nexus5,现在又是Oneplus5,闲来无事就爱刷机. 昨天看OnePlus官网的氧OS更新到Android9.0,于是又开启了刷机旅程. 显然这次没有之前那么顺利, ...
- 编写MyCP.java 实现类似Linux下cp XXX1 XXX2的功能
题目:编写MyCP.java 实现类似Linux下cp XXX1 XXX2的功能 要求:MyCP支持两个参数: java MyCP -tx XXX1.txt XXX2.bin 用来把文本文件(内容为十 ...
- sortable.js 拖拽排序及配置项说明
// 拖动排序 $(function() { /*排序*/ //排序 // Simple list ]; new Sortable(list, { group: "name", a ...
- http强转https websocket
需要在httpd.conf文件最后添加即可: <Directory /> Options FollowSymLinks AllowOverride All RewriteEngine on ...
- 公设基础Generic
1# 与泛型相关的一些术语 1.类型参数(type parameter) : EX: List<E> 这里的E就属于List接口的单个类型参数E 2.参数化的类型(parameterize ...
- 强连通分量Kosaraju
#include<cstdio> #include<algorithm> #include<iostream> #include<cstring> #i ...