在查阅google之后,我发现没有一处对并发算法或是数据结构规定的演进条件(progress condition,注:参考[1],译者认为翻译为演进状态更为合适)做合理的解释。甚至在”The Art of Multiprocessor Programming“中也只有围绕书本的一小段定义,大部分定义是单行的句子,因而造成了我们普通人含义模糊的理解,所以在这里我把我对这些概念的理解整理在一起,并且在每一种概念后面给出相应的例子。
我们先将演进条件分为四个主要类别,阻塞(blocking),无干扰(obstruction-free),无锁(lock-free),和无等待(wait-free)。详细列表如下:
Blocking
1. Blocking
2. Starvation-Free
Obstruction-Free
3. Obstruction-Free
Lock-Free
4. Lock-Free (LF)
Wait-Free
5. Wait-Free (WF)
6. Wait-Free Bounded (WFB)
7. Wait-Free Population Oblivious (WFPO)
在我们开始解释每一个术语的意义之前,让我们先了解一些相关细节。
根据《The Art of Multiprocessor Programming》一书3.6节中的相关描述,我们很难理解有界无等待(wait-free bounded,注:参考[1])和集居数无关无等待(wait-free population oblivious,注:参考[1])的概念是否相关。就我个人而言,我认为一个算法如果满足集居数无关无等待,就意味着这个算法是有限界的,从而这个算法也满足有界无等待。这代表一个方法(或者是一个算法,一个类)可以是无等待,或者有界无等待,或者集居数无关无等待,最后一个条件是所有演进条件中“最好”的。
在这篇文章中我们讨论的例子都是关于某个(一个算法中,或一个类中)的特定方法的演进条件,而不是完整的算法。我们这样做的原因是一个算法中不同的方法可能会采用不同的演进保证(progress guarantees),举个例子:(某个算法)写操作可能是阻塞的,然而读操作是满足集居数无关无等待,这样的情况在你第一次观察某个无锁和无等待数据结构的时候并不是显而易见的。
是的,你的结论是正确的:一个数据结构中可能有某些操作是阻塞的,另外一些操作是无锁的,剩下的甚至是无等待的。另一方面,当我们称某个特定的数据结构是无锁,这代表这个数据结构的所有操作都是无锁,或者更好的状态(无等待或者更好)。
事实上在著作中没有严格对那种状态更好有严格的定义,但是一般而言说是这样的:
无等待>无锁>阻塞
上述说法的理由是:现实中并没有太多无锁或者是无等待的(实用)数据结构,所以通常不会出现关于演进状态合理分类的问题。Andreia和我提出了很多不同种类的并发算法,其中混合了各种演进状态。因此我们需要对这些演进状态合理地命名,并且对他们排序,从而继续对他们的处理,并且发现哪些算法值得研究。
我们提出的顺序就是文章开头描述的列表,表中阻塞是最差的,集居数无关无等待是可能达到的最好状态。
另一个通常会导致困惑的说法是“无等待意味着无锁”(但是不能反过来说)。这意味着一个方法如果满足无等待,那么这个方法有着和一个无锁方法同样的演进保证。下图是一张venn图,可能能够更好的解释我们的意思。
这张图展示了算法的集合,图中无等待的算法也是无锁算法的一部分。
1. 阻塞
阻塞是大家所熟知的。基本所有加锁的算法都可以说是阻塞的。某个线程所引起的意外延迟会阻止其他线程继续运行。在最坏的情况下,某个占有锁的线程可能被睡眠,从而阻止其他等待锁释放的线程进行接下来的任何操作。
定义:
一个方法被称为阻塞的,即这个方法在其演进过程中不能正常运行直到其他(占有锁的)线程释放。
例子:
循环中对拥有两个状态的变量的简单CAS操作
01 |
AtomicInteger lock = new AtomicInteger( 0 );
|
02 |
03 |
public void funcBlocking() {
|
04 |
05 |
while (!lock.compareAndSet( 0 , 1 )) {
|
06 |
07 |
hread.yield();
|
08 |
09 |
}
|
10 |
11 |
} |
2. 无饥饿
(无饥饿)有的时候也被称为无闭锁。这是一个独立的性质,只有当底层平台/系统提供了明确的保障以后讨论这个性质才有意义。
定义:
只要有一个线程在互斥区中,那么一些希望进入互斥区域的线程最终都能够进入互斥区域(即使之前在互斥区中的线程意外停止了)。
例子:
一个严格公平的互斥锁通常是无饥饿的。
在JDK 8中的StampedLock有这样的性质,因为它创建了一个线程队列(链表)等待获取锁。这个队列的插入操作是无锁的,但是在插入之后,每个线程都会自旋或者让步从而被当前占有锁的线程锁阻塞。释放锁的线程采用unsafe.park()/unpark()机制,够唤醒下一个在队列中等待的线程,从而执行了严格的优先级。这个机制的意义是,如果给予其他线程(占有锁的线程)足够的时间去完成他们的操作,那么当前线程可以确保最终获取锁,然后完成自己的操作。
有关StampedLock的源码详见这里:
3. 无干扰
这是一个非阻塞性质。关于无干扰和无饥饿的更多细节可以查看《The Art of Multiprocessor Programming》(revised edition)的第60页。
定义:
如果一个方法满足无干扰性质,那么这个方法从任意一点开始它的执行都是隔离的,并且能够在有限步内完成。
例子:
我所知道的唯一例子就是Maurice Herlihy,Mark Moir和Victor Luchangco所提出的Double-ended Queue。
http://cs.brown.edu/~mph/HerlihyLM03/main.pdf
4. 无锁
无锁的性质保证了至少有一个线程在正常运行。在理论上这代表了一个方法可能需要无限的操作才能完成,但是在实践中只需要消耗很短的时间,否则这个性质就没有什么价值了。
定义:
如果一个方法是lock-free的,它保证线程无限次调用这个方法都能够在有限步内完成。
例子:
一个调用CAS操作的循环增加原子整形变量。
01 |
AtomicInteger atomicVar = new AtomicInteger( 0 );
|
02 |
03 |
public void funcLockFree() {
|
04 |
05 |
int localVar = atomicVar.get();
|
06 |
07 |
while (!atomicVar.compareAndSet(localVar, localVar+ 1 )) {
|
08 |
09 |
localVar = atomicVar.get();
|
10 |
11 |
}
|
12 |
13 |
} |
另外一个比较著名的例子是java.util.concurrent中的ConcurrentLinkedQueue,其中add()和remove()操作是无锁的。
5. 无等待
无等待性质保证了任何一个时间片内的线程可以运行,并且最后完成。这个性质保证步骤是有限的,但是在实践中,这个数字可能是极大的,并且依赖活动的线程数目,因此目前没有很多实用的无等待数据结构。
定义:
假如一个方法是无等待的,那么它保证了每一次调用都可以在有限的步骤内结束。
例子:
这篇论文给出了一个无等待(有界无等待)算法的例子。
http://www.cs.technion.ac.il/~moran/r/PS/bm94.ps
6. 有界无等待
任何一个有界无等待的算法,也是无等待的(但并不一定是集居数无关无等待的)。
定义:
如果一个方法是有界无等待的,那么这个方法保证每次调用都能够在有限,并且有界的步骤内完成。这个界限可能依赖于线程的数量。
例子:
一个扫描/写入到长度和线程数目相关的数组的方法。如果数组中每个条目的操作数是常量,那么显然这个方法是有界无等待的,并且不是集居数无关无等待,因为数组的长度和线程的数目有关。
01 |
AtomicIntegerArray intArray = new AtomicIntegerArray(MAX_THREADS);
|
02 |
03 |
public void funcWaitFreeBounded() {
|
04 |
05 |
for ( int i = 0 ; i < MAX_THREADS ; i++) {
|
06 |
07 |
intArray.set(i, 1 );
|
08 |
09 |
}
|
10 |
11 |
} |
7. 集居数无关无等待
这个性质用来描述这些在一定数量步骤内完成一些指令,并且指令数目与活动线程数目无关的方法。任何一个集居数无关无等待的方法都是有界无等待的。
定义:
一个无等待的方法,如果其性能和活动线程数目无关,那么被称为集居数无关无等待的。
例子:
最简单的例子是使用fetch-and-add原语(在X86 CPU上是XADD指令)增加一个原子变量。这个操作可以用C11/C++11中的fetch_add()原子方法完成。
1 |
atomic counter; |
2 |
3 |
void funcWaitFreeBoundedPopulationOblivious() {
|
4 |
5 |
counter.fetch_add( 1 );
|
6 |
7 |
} |
结论
上述的这些并不是问题的全部。我们忽略了两个了解全貌需要掌握的知识点:
第一点,如果你的方法需要分配内存,那么这个方法可以提供的演进保证在实际中受到内存分配机制的演进条件所限制。我认为,我们需要针对方法是否需要分配内存进行不同的分类。你可以在这篇文章中了解更多的细节,但是基本的思想是创造一个集居数无关无等待的,并且一直需要用阻塞机制分配内存的方法没有太大的意义。
第二点,关于演进条件的完整概念是用于将算法和方法按照时间保证分类,但是这些定义却是基于操作数目。这基于一个操作的完成时间与活动线程数目无关这个假设的,这些假设在单线程代码中是正确的,但是在多线程程序中将会失效。
CPU缓存一致性的工作机制,将会导致多线程/多核访问(原子)变量的竞争,从而使得一个操作/指令在一定情况下(因为cache-miss)需要相对更长的时间才能完成。如果你不相信我的话,可以看一看这篇文章,这就是为什么许多wait-free数据结构的实现要比lock-free的相同数据结构更慢的主要的原因(或者说主要原因之一),尽管他们对执行的操作总次数有更强的保证,每一个操作因为竞争的因素却可能要用很长的时间去完成……还可能是他们平均执行的操作次数更多。
总的来说,对于倾向于数学的读者,这里有我提出的定义:
设F为一个函数方法
设L为同时调用F的并发线程数目
设N为一个与L无关的变量
设OpsF()代表一个指定线程完成F需要进行的操作数目。
设C(n,L)为一个依赖N和L的函数
当任何有限值L满足以下条件时,F方法是对应的进行状态:
Lock-free:
如果至少有一个L线程在有限步骤内完成操作;OpsF() < C(N,L)
Wait-free:
如果所有的L个线程在有限的步骤内完成操作:OpsF() < C(N,L)
Wait-free bounded:
如果所有的L个线程消耗C(N,L)或者更少的时间完成操作:OpsF() < C(N,L)
Wait-free population oblivious:
如果所有的L个线程在有限操作内完成F,并且和L无关:OpsF() < C(N)
在实践中,“无等待”和“有界无等待”的区别很小,这篇论文中有很好的解释:
http://www.cs.technion.ac.il/~moran/r/PS/bm94.ps
另一个细节是,并没有“集居数无关无等待”(的数据结构,注:准确的话说是没有一个所有方法都是集居数无关无等待的数据结构),因为达到集居数无关的状态意味着,F方法有一个使其运行结束所需的最坏操作数目上限。
希望这篇文章可以帮助你理解lock-free和wait-free的区别。
参考
[1] 多处理器编程的艺术,http://book.douban.com/subject/3901836/
转自:http://ifeve.com/lock-free-and-wait-free/?utm_source=tuicool
相关推荐
项目中常见的问题,记录一下解决方案
2024-2025第一学期一上U1~3.pdf
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sortedset --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。感兴趣的朋友可以过来看看
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。
全国月尺度平均气温数据集(1961-2022, 0.25° × 0.25°)是一个高分辨率的网格化平均气温数据集,覆盖了中国大陆及周边地区。 该数据集通过科学方法整合气象观测和再分析数据,为气候研究、生态模型、农业生产、以及水资源管理等领域提供了重要支持。 数据下载后可显示详细信息。
yolo系列算法目标检测数据集,包含标签,可以直接训练模型和验证测试,数据集已经划分好,包含数据集配置文件data.yaml,适用yolov5,yolov8,yolov9,yolov7,yolov10,yolo11算法; 包含两种标签格:yolo格式(txt文件)和voc格式(xml文件),分别保存在两个文件夹中; yolo格式:<class> <x_center> <y_center> <width> <height>, 其中: <class> 是目标的类别索引(从0开始)。 <x_center> 和 <y_center> 是目标框中心点的x和y坐标,这些坐标是相对于图像宽度和高度的比例值,范围在0到1之间。 <width> 和 <height> 是目标框的宽度和高度,也是相对于图像宽度和高度的比例值
模拟退火算法shell脚本编程实践,分享给有需要的人,仅供参考。内容来源于网络分享,如有侵权请联系我删除。另外如果没有积分的同学需要下载,请私信我。
基于PCIe接口的高性能视频编辑系统
python爬虫入门,分享给有需要的人,仅供参考。内容来源于网络分享,如有侵权请联系我删除。另外如果没有积分的同学需要下载,请私信我。
在线音乐网站的设计与实现
电工与电子技术课程标准.doc
根据搜索结果,以下是一条关于社科数据的内容介绍:本数据集涵盖了2015至2021年间全国各地级市的关键经济指标,包括人口数量、城镇居民人均可支配收入、进出口总额以及社会消费品零售总额。这些数据为研究区域经济发展提供了宝贵的信息资源,来源于各省市统计年鉴及国家统计局的官方数据,确保了数据的权威性和准确性。数据内容全面,缺失值较少,适合用于宏观经济分析、政策评估以及学术研究等多个领域。通过这些数据,研究者可以深入了解中国各地区在不同年份的经济表现和发展趋势。
项目中常见的问题,记录一下解决方案
yolo系列算法目标检测数据集,包含标签,可以直接训练模型和验证测试,数据集已经划分好,包含数据集配置文件data.yaml,适用yolov5,yolov8,yolov9,yolov7,yolov10,yolo11算法; 包含两种标签格:yolo格式(txt文件)和voc格式(xml文件),分别保存在两个文件夹中; yolo格式:<class> <x_center> <y_center> <width> <height>, 其中: <class> 是目标的类别索引(从0开始)。 <x_center> 和 <y_center> 是目标框中心点的x和y坐标,这些坐标是相对于图像宽度和高度的比例值,范围在0到1之间。 <width> 和 <height> 是目标框的宽度和高度,也是相对于图像宽度和高度的比例值
6
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。
S家LPDDR5/4/4X 控制器,针对功耗、延迟、带宽和面积进行了优化,支持 JEDEC 标准 LPDDR5、LPDDR4 和 LPDDR4X SDRAM。控制器通过 DFI 5.0 接口连接到 S家LPDDR5/4/4X PHY 或其他 LPDDR5/4/4X PHY,以创建一个完整的内存接口解决方案。S LPDDR5/4/4X 控制器包括软件配置寄存器,可通过 AMBA 3.0 APB 接口访问。 // Key Used : DWC-DDRCTL (IP access) // Key Used : DWC-LPDDR54-CONTROLLER (Add-on feature access: DWC LPDDR5/4/4X Controller) 注意:压缩包只有IP使用文档,完整IP及无加密SV代码压缩包有获取方式。
java桌面小程序,主要为游戏.zip学习资源VM
全国各省、市、县农业保险绿色保险收入支出赔付率统计数据集涵盖了2002至2020年间的详细数据。该数据集包含全国31个省、自治区、直辖市的农业保险收入、支出、保险总支出、农业保险规模占比以及农业保险赔付率等关键指标。此外,数据还涉及341个地级市的农业保险收入和支出年度数据,时间跨度从2002年到2020年。特别值得一提的是,数据中还包括了县级政府农业保险补贴数据,覆盖了产粮大县726个和非产粮大县755个,时间范围为2016至2018年。这些数据均来源于历年中国保险年鉴,并经过手工整理,提供了农业保险规模占比与农业保险赔付率等重要指标。此数据集为研究中国农业保险市场的发展、政策效果评估以及风险管理提供了宝贵的实证资料。
中医诊所系统,WPF.zip