Art of Multiprocessor Programming 答案 ch7

本文探讨了多种并发控制及锁机制中的问题,包括死锁、饥饿、互斥等问题,并对比了不同锁机制如TAS、CLH和MCS的实现方式。分析了复合锁在竞争激烈时的表现以及层次锁在特定条件下的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

85. 因为线程自己的节点还被后续节点和tail引用。

被后续节点引用的case: 2个线程A,B

A<--B

B.lock() --> B.pred=A --> B.pred.locked=true --> A.unlock() --> A.locked = false 

==> A wants lock again : A.lock() --> A.locked=true --> A.pred = B --> A.pred.locked()=true 

==> B wakes up: B.pred.locked =true --> A.pred.locked=true

==> deaklock

被tail引用:

tail=A --> A.lock() --> A.unlock() --> A.lock() --> A.locked=true --> A.pred=tail.getAndSet(A)

==> A.pred=A --> deadlock


86. 第二种比较好。

第一种实现在最好的情况下,比如说同步程度非常高,则每个线程占用总线实现cache miss --> read --> update --> write;然后每个线程重新load得到已经更新到过路障的值,总共有n次回写,n次cache miss。一般的情况下,每一次更新都可以类比于tta锁的unlock,带来n个cache miss,即n^2次cache miss。

第二种情况下,最坏的情况下每个更新只引起一个回写和一个cache miss;加上最后一个线程引起n个cache miss;也是n次回写,2n次cache miss。


87. 互斥

如果已经有一个线程拥有fastpath锁,与它竞争的线程在竞争fp锁的时候或者(oldStamp & FASTPATH) != 0 或者 tail.compareAndSet会失败。继续慢速通道的时候会有pred需要等,或者有fastPathWait()需要等fp锁释放。

如果已经有一个现成拥有慢速锁,与他竞争的线程在试图得到fp锁的时候会发现 qnode != null; 继续走慢速通道时根据普通compositeLock互斥。

饥饿

CompositeLock只有在抢到了队列中的节点并加入tail链之后才会开始排队等锁,在抢队列节点和加入tail的时候是没有公平性的。所以在acquireQNode和spliceQNode时候的CAS都可能引起饥饿。

同时,fastPathUnlock()中的CAS也可能引起所有其他线程的饥饿,因为不断有线程timeout,不断有现成开始抢锁,导致tail不断被更新,使这个cas退不出来。


88. 如果这个算法不正确,错误的case应该是这个关键节点同时使localqueue和globalqueue的preNode,如果localqueue一直不知道自己是master,localqueue就不能被加入globalqueue中,最后的关系如下所示:

node<-- ... <-- current node(A) <-- master of another cluster(B) <-- other nodes of another cluster 

                        /|\____ (master) of local queue(C) <-- other nodes of my cluster

则当current node释放锁的时候,可能会有2个master同时进入critical section,破坏互斥。

但是这是不可能发生的,因为C将自己加到localqueue以后,要执行myPred.waitForGrantOrClusterMaster()。这个方法使C等到A放锁或者发现自己是local master。但是A在放锁之前一定会先执行 localTail.setTailWhenSpliced(true),即B在发现A.isSuccessorMustWait() == false之前一定有A.isTailWhenSpliced() == true;所以在进入critical region之前会先履行master的职责。


89. 会出现每次每一个cluster只有一个node被加到globalqueue中的情况,即完全没有体现层次锁的优点。可以加一个timer,如果master发现localqueue上的节点太少,时间也充分就等待一段时间。


90. 有一种case可能会出问题:即 ClusterA中的NodeA1已经是localqueue(A)中的tail,加入到globalQueue后被ClusterB中的NodeB回收;同时NodeA2要加到localqueue(A)中。

如果NodeB执行NodeA1.unlock()时,不是原子的同时更改clusterId, SMW和TWS,而是先更改TWS=false。则NodeA2.waitForGrantOrClusterMaster()会发现

getClusterID() == myCluster && !isTailWhenSpliced() && !isuccessorMustWait()

==〉NodeA2会获得锁,即使当前globalqueue上还有其他等待的Node。

如果NodeA1.unlock()时先设置clusterId,除非cpu决定乱序执行,否则应该可以不用原子的修改state。因为如果NodeA1.unlock()是设置的clusterId等于原clusterId,则NodeA2从localqueue(A)中得到的myPred != NodeA1; 如果不等于原clusterId,NodeA2.waitForGrantOrClusterMaster()会发现NodeA2是master,然后从globalQueue去锁。


91.

TAS

boolean isLocked()
{
    return state.get();
}

CLH:

boolean isLocked()
{
    return tail.get().locked;
}

MCS:

boolean isLocked()
{
    QNode node = queue.get();
    if(node == null)
    {
      return false;
    }else
    {
      return node.locked;
    }
}

92. 原来的证明过程中现成必须先写某个寄存器然后判断全局状态,如果用R-M-W操作,比如CAS,可以先读然后决定是否写,所以可以避免覆盖的情况。

《C++编程实例100篇》是一本深入实践、极具价值的编程教程,它针对C++编程语言提供了丰富的实例,旨在帮助读者更好地理解和掌握C++的各项特性与编程技巧。这本书的经典之处在于它将理论与实践相结合,通过100个精心设计的编程实例,覆盖了C++的各个核心领域,包括基础语法、面向对象编程、模板、异常处理、STL(标准模板库)等。 我们来探讨C++的基础语法。C++是C语言的增强版,它保留了C语言的高效性和灵活性,并引入了类、对象和继承等面向对象编程概念。基础语法包括变量声明、数据类型、运算符、控制结构(如if语句、for循环、while循环)、函数的定义和调用等。在实例中,你可能会遇到如何编写简单的程序,如计算两个数的和,或者实现一个简单的猜数字游戏。 C++的面向对象编程是其一大特色。通过类和对象,你可以构建复杂的软件系统。类是对象的蓝图,它定义了对象的属性和行为。实例化一个类,就是创建一个具体的对象。继承允许你创建新的类,这些类从现有的类派生,共享其属性和方法,同时可以添加新的功能。多态性是面向对象的另一个关键特性,它使得不同类型的对象可以对同一消息作出不同的响应。这些概念在实例中会以各种形式展现,例如设计一个图形界面的类层次,或实现一个简单的模拟游戏。 接下来是模板,C++的模板功能让代码更加通用,可以处理不同类型的数据。模板分为函数模板和类模板,前者可以创建泛型函数,后者可以创建泛型类。通过模板,你可以编写出高效且灵活的代码,比如实现一个通用的排序算法。 异常处理是C++中用于处理程序运行时错误的机制。当程序出现异常情况时,可以抛出一个异常,然后在适当的点捕获并处理这个异常。这使得代码能够优雅地处理错误,而不是让程序崩溃。实例中可能会有涉及文件操作或网络通信时可能出现的异常处理示例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值