AMP学习笔记
第1章 引言
-
本书重点讲述:共享存储器通信方式下的多处理器编程技术,这样的系统称为共享存储器的多处理器,现在也称之为多核。
-
本书原理部分着重于可计算性理论:理解异步并发环境中的可计算问题。理解可计算性的关键在于描述和证明特定程序的实际执行行为,更准确的说,即程序正确性问题。程序正确性主要关心两个概念:
-
安全性:说明了“不好的事情”绝不会发生;
-
活性:是指一个特定的“好的事情”一定会发生。
-
-
一些概念
-
互斥:两个或两个以上的事物不能同处于一状态(临界区)
-
无死锁:有事物能进入某状态(临界区)
-
无饥饿:任何事物都能进入某状态(临界区)
-
等待:
-
容错:在某事物出现错误时,系统如何处理
-
-
通信的两种类型
-
瞬时通信:要求通信双方在同一时间都参与通信。
-
持续通信:允许发送者和接受者在不同时间参与通信。
-
-
Amdahl定律
并行完成复杂工作可获得的加速比试有限的。
S= oldExcecutionTime / NewExcecutionTime =1/(1-p+p/n),其中
S:加速比 p:可并行部分比例
n:n个处理器
-
异步性
无法确定线程的下一步动作。
第2章 互斥
-
本章重点
介绍了基于读/写共享寄存器模式的四种经典互斥算法:LockOne, LockTwo,Peterson, Barkery。还使用了数学符号证明算法的互斥性,以及介绍了“不可能性”的证明方法(存储单元数量的下界)。
-
线程状态的转换称为事件。事件是瞬间的。
事件用字母表示,aij表示事件ai第j次发生
Interval(a0,a1):表示a0和a1之间的间隔
a → b:表示事件a在事件b之前发生
多个不存在“→”关系的间隔称为并发
CSjA:表示A第j次执行临界区的时间段
-
好的锁算法应该满足的特性
-
互斥
-
无死锁
-
无饥饿(可以不保持)
-
-
LockOne vs LockTwo vs Peterson
LockOne并发执行时会死锁;如果一个线程完全先于另一个线程,则不会死锁
LockTwo并发执行时不会死锁;如果一个线程完全先于另一个线程,则会死锁
Peterson结合了LockOne、LockTwo,工作正常,且有先来先服务的特性
-
两种支持n(n>2)线程的互斥协议
-
过滤锁
-
Bakery锁
-
-
lock()方法的代码划分为两部分:
-
门廊区,其执行区间DA由有限个操作步组成
-
等待区,其执行区间WA可能包括无穷个操作步
-
有界无等待演进特性:门廊区DA在有限步内完成
先来先服务:若DjA → DjB ,则CSjA → CSjB
-
读/写共享寄存器模式的无死锁互斥算法最少需要n个不同的存储单元
-
Lock对象的状态s是不一致:你无法确定状态s是有锁的还是无锁的。
无死锁的Lock算法不可能进入不一致状态。
第3章 并发对象
-
本章重点
本章讲述了并发对象的正确性(安全性)和演进性(活性)。正确性,考虑了三种正确性条件:静态一致性、顺序一致性、可线性化特性。
-
静态一致性适合于以相对较弱的对象行为约束代价获得高性能的应用
顺序一致性是一种较强的约束限制,通常用于描述类似于硬件存储器接口这样的底层系统
可线性化特性是一种更强的约束,适用于描述由可线性化组件构成的高层系统。
-
正确性分析的关键准则:
如果能将并发执行转化为顺序执行,则只需对该顺序执行进行分析,从而简化了并发对象的分析
-
顺序对象可以通过API文档来描述:
API →(前置条件)方法调用(后置条件)
但并发对象不能使用这种描述,因为它的方法调用是重叠的,没用明显的(前置)后置条件。
-
静态一致性
-
定义
-
方法调用呈现某种顺序,且瞬时发生
-
静态状态前的方法调用效果先于静态方法后的方法调用
-
-
特性
-
阻塞:非阻塞
-
-
非阻塞定义:一个完全方法的未决调用不需要等待另一个未决调用完成。
-
复合:可复合
可复合定义:对于正确性p,如果系统每一部分都满足p,则整个系统也满足p。
-
顺序一致性(两个线程时间无关,单线程时间有用)
-
定义
-
方法调用呈现某种顺序,且瞬时发生
-
方法调用效果应同于程序调用效果(输入输出相一致)
-
-
特性
-
复合:不可复合
-
阻塞:非阻塞
-
-
-
可线性化形
-
定义
-
在顺序一致性的基础上,方法的调用效果必须符合调用和响应时间。
-
特性
-
可复合
-
非阻塞
-
-
可线性化点:方法生效的地方
通常是该方法调用的结果对其他方法调用可见时的那个操作步。
-
三种正确性条件的关系
-
静态一致性与顺序一致性无关
-
可线性化性比静态一致性、顺序一致性更高一级,是他们两的结合
-
-
形式化定义
-
经历:是方法的调用事件和响应事件的一个有限序列
-
方法调用记作 <x.m(a*) A> ,方法响应记作 <x:t(r*) A>
-
其中x是对象,m是方法名,a*是参数序列,A是线程,t或者是Ok或者是一个异常名,r*是结果值序列
-
经历H的扩展:对H中的零个或多个未决调用增加了相应的响应后构成的经历
complete(H)是全部由匹配的调用和响应所构成的H的子序列。
-
H|A:H中线程所用线程名为A的事件组成的子序列。
H|A=H`|A,则H和H`等价
H中的方法调用m0先于m1,记作m0 →H m1
-
可线性化性的形式化定义
如果经历H存在有一个扩展H`及一个合法的顺序经历S,并使得
-
complete(H`)与S等价,且
-
若在H中方法调用m0先于m1,那么在S中也成立
-
一些概念
阻塞:一个线程的意外延迟将会阻止其他线程继续推进
无等待:方法的每次调用执行都能在有限步内完成
有界无等待:在无等待的基础上,方法的操作步个数存在界限
对象/类无等待:对象的方法都是无等待的(类中的对象都是无等待的)
无锁:一个方法的无限次调用至少有一个调用能够在有限步内完成
无死锁:
无饥饿:
无干扰:若一个方法能在有限步内完成,并且从任一点开始,它的执行都是隔离的,则这个方法是无干扰的
第4章 共享存储器基础
-
本章重点
本章重点讲述由简单的寄存器构造更复杂的寄存器
-
线程是异步的,它们各自以不同的速度执行,且在任意时刻可以停止一个不可预测的时间间隔。
-
不能使用互斥来实现共享存储器的并发调用,因为:
-
互斥是通过起存起来实现的
-
寄存器是并发计算的基础块,应具有无死锁或无饥饿的演进特性。
-
-
寄存器的实现应该是无等待的。
-
寄存器的构造序列
SRSW安全寄存器
MRSW安全寄存器
MRSW规则布尔寄存器
M-值MRSW规则寄存器
SRSW原子寄存器
MRSW原子寄存器
MRMW原子寄存器
原子快照
-
原子快照
SeqSnapshot:使用同步(Sysnchronized)
SimpleSnapshot:void update()无等待,T[] scan()无锁,无饥饿
WFSnapshot:无等待
第7章 自旋锁与争用
-
本章重点
研究采用自旋转技术的锁,介绍了测试-设置锁、测试-测试-设置锁、指数后退锁、基于数组的锁、CLH队列锁、MCS队列锁、时限队列锁、复合锁
-
当呼哧协议不能获得锁时的两种选择
-
自旋转:对锁反复测试
-
阻塞:切换到另一个线程
-
-
本地旋转指线程反复的重读被缓存的值而不是反复的使用总线。
-
争用:指多个线程试图同时获取一个锁
高争用:存在大量正在争用的线程
低争用:存在少量正在争用的线程
-
各种锁的比较
锁的类型 |
优点 |
缺点 |
空间复杂度 |
TAS/TTAS |
简单 |
速度慢,可扩展性差 |
O(1) |
BackOffLock |
易于实现,性能比TTAS好 |
常量难定,难跨平台 |
O(1) |
ALock |
先进先出,可扩展性好 |
“假共享”,需要知道n |
O(Ln) |
CLHLock |
空间消耗小,仅相续线程影响 |
在NUMA系统中性能差 |
O(L+n) |
MCSLock |
可适用于NUMA系统 |
释放锁也要旋转 |
O(L+n) |
TOLock(时限锁) |
|
|
|
CompositeLock(复合锁) |
|
|
|
层次锁 |
适合于集群处理器 |
|
|
第8章 管程和阻塞同步
-
本章重点
本章讲述阻塞同步,首先介绍管程的观念,之后用阻塞的方法实现了读者-写者锁,最后介绍了可重入锁和信号量。
-
把自旋和阻塞结合起来往往是有意义的:对于正在等待出队元素的线程,可以先让他自选一小段时间,如果延迟较长则切换为阻塞。
-
管程:将方法、互斥锁和条件对象组合在一起的整体称为管程。
-
唤醒丢失问题:一个或多个线程一直在等待,而没有意识到他们所等待的条件已变为true。
-
可重入锁:如果一个锁能被同一个线程多次获得,则称该锁是可重入的
第9章 链表:锁的作用
-
本章重点
本章介绍了几种锁和一种非阻塞的实用技术,有粗粒度同步、细粒度同步、乐观同步、惰性同步、非阻塞同步。它们对锁的依赖越来越小,并发度越来越高。
-
各种锁的思想
-
粗粒度同步:用一把锁锁住所有对象
-
细粒度同步:将对象分解成一些独立的同步组件,并确保只有当多个方法调用试图同时访问同一个组建时才发生冲突
-
乐观同步:在查找时无需获取任何锁,如果方法找到所需的部分,它就锁住该组件,然后确认该组件在被检测和上锁期间没有发生变化。
-
惰性同步:有时将较难的工作推迟完成是一种好的处理方式。
-
非阻塞同步:有时可以完全清除锁,依靠类似compareAndSet()的内置原子操作进行同步。
-
-
并发数据结构的不变式:指数据结构一直保持的特性
-
惰性同步的优点之一就是能够将类似于设置一个flag这样的逻辑操作与类似于删除节点的链接这种对结构的物理改变相分开。
第10章 并行队列和ABA问题
-
本章重点
本章主要讲述数据结构队列,其中介绍了部分有节队列(lock)、完全误解队列(lock)、无锁无界队列、双重队列,同时还介绍了内存回收技术和ABA问题。
-
对象池(pool)
只需要提供插入、删除操作,不需要contains()方法来检测池的成员。池可以当缓冲区使用。
-
池的几种刑事变化:
-
池可以是有界或无界。此界限为池的容量
-
池的方法可以是完全、部分或同步的
-
池提供各种不同的公平保证,如队列的先进先出(FIFO)、栈的后进先出(LIFO)。
-
-
管理内存回收的优点
-
C和C++这些语言并不支持垃圾回收
-
提供自己的内存回收管理效率更高
-
若垃圾回收进程不是无锁,希望提供自己的无锁内存回收
-
-
管理内存回收方法:维护一个自己的私有空闲链
-
条件同步操作的动态内存算法会出现ABA问题,但链接加载/条件存储(LL/SC)可以防止ABA问题。
-
生产者与消费者会和:向队列中放入一个元素的生产者应阻塞直到该元素被另一个消费者取出。这就是方法的同步。
-
双重数据结构
第11章 并发栈和消除
-
本章重点
本章介绍了基本数据结构——栈,并介绍了栈的两个无锁算法。
-
栈的性质:后进先出
-
LockFreeStack算法的可扩展性非常差
-
栈的top域是一个争用源
-
这种栈存在顺序瓶颈:方法调用只能一个接一个的前进
-
-
解决方案
让一对push和pop相互消除,这样就不必要进栈了,而且可以提高并发。
第12章 计数、排序和分布式协作
-
本章重点
把看似顺序的问题,将其协作任务“分散”在多个部件来使它们具有高度的并行性。本章介绍的分布式协作模式:组合、计数、衍射和样本。两种数据结构:树和组合网络。
-
评测数据结构的性能:一是时延,二是吞吐量
-
粗粒度锁的不足
-
会产生顺序瓶颈
-
导致内存有争用热点
-
-
提高并行性的挑战
-
必须防止内存争用
-
必须获得真正的并行性
-
-
组合数的优缺点
-
缺点:单个方法调用的时延较大
-
优点:
-
有很好的吞吐量
-
能对树进行任意的值交换,而不是仅仅的递增(加1)
-
-
-
组合树的性能:依赖组合速率,与并发的程度
-
深度
组合数
LogW
计数网
Log2W
衍射树
LogW
排序树
LogW
-
平衡器:具有两条输入和输出的交换机
平衡器绝不会创建令牌:X0+X1≥Y0+Y1
平衡器是静态的:X0+X1=Y0+Y1
-
步进特性
三种定义,等价。设y0,…,yw-1是一系列非整数,
-
对任意i<j,有0≤yi-yj≤1。
-
要么对于所有的i,j有yi=yj,要么存在一个c使得对任意的i<c,j≥c有yi-yj=1。
-
如果m=∑yi,则yi=⌈(m-i)/w⌉
-
两种排序算法
-
排序网:适用于内存中较小的数据集合
-
样本排序算法:适用于外存上的大量数据集
-
-
比较器:是同步的