习题9
对于一个给定的互斥算法,定义r-有界等待为:如果DjA -> DkB,则CSjA->CS(k+r)B。是否存在一种定义Peterson算法门廊的方法,使得对于某个值r,改算法能够支持r-有界等待?
解答:
这里r-有界等待的定义与先来先服务的定义有些类似。在Peterson算法中,个人认为是没有必要添加门廊的,因为算法只控制两个线程或进程,在门廊中等待的线程或进程永远都只有一个,那么原算法中的flag数组就够用了;因为,原算法已经是有界等待的,所以r应该有值,能让算法支持r-有界等待。
习题10
为什么需要定义门廊区?为什么不能在基于lock()方法中第一条指令被执行的次序的互斥算法中定义先来先服务(FCFS)?根据lock()方法执行第一条指令的方式——对不同单元或相同单元的读和写,逐一证明你的结论。
解答:
门廊区是要保证进入该区域内的线程,在一定的步限内,能进入临界区。
在2.8节中,定义2.8.2提示到,Lock对象的覆盖状态:至少有一个线程欲写所有的共享存储单元,而该Lock对象存储单元,临界区“看上去”就好像是空的(也就是说,这些存储单元的状态就像既没有线程在临界区内,也没有线程尝试访问临界区一样)。
根据这个定义,我们判断出,锁的触发必须要有写入操作,但是,在lock()中的第一条指令,我们没有办法判断,这次操作是 读 操作,还是 写 操作,就无法判断先后了。
PS. 当写之后没有读的操作,就等于没有线程去看这个数据。当读写都有的时候,第一条指令就可以看作门廊区的标识。
习题11
Flaky计算机公司的程序员设计了一个如图所示的协议,以保证n线程的互斥。对于以下每个问题,或证明其成立,或给出一个反例。
1 class Flaky implements Lock {
2 private int turn;
3 private boolean busy = false;
4 public void lock() {
5 int me = ThreadID.get();
6 do {
7 do {
8 turn = me;
9 } while (busy);
10 busy = true;
11 } while (turn != me);
12 }
13 public void unlock() {
14 busy = false;
15 }
16 }
①该协议满足互斥特性吗?
②该协议是无饥饿的吗?
③该协议是无死锁的吗?
解答:
这个代码写的和LockTwo很像,证明方法可以参考LockTwo。
①证明:
假设不成立,考虑每个线程在第k次(第j次)进入临界区前最后一次调用lock()方法的执行情形。
通过代码可以看出
writeA(turn = A) -> readA(turn == B) (1.1)
writeB(turn = B) -> readB(turn == A) (1.2)
线程B必须在事件writeA(turn = A)和事件readA(turn = B)之间将B赋给turn。由于这最后一次赋值,所以有:
writeA(turn = A)->writeB(turn = B) ->readA(turn == B)
一旦turn被设置为B,则将保持不变。所以,随后的读操作都将返回B,与公式1.2矛盾。
所以,该协议满足互斥特性
②证明:
假设不成立。假定(不失一般性)线程A一直在之子那个lock()方法,那么它必定在执行while语句,等待busy被设置为false和turn被赋值为B。
当A不能前进是,线程B可能多次进入临界区,这样,线程B一旦进入临界区将会使turn设置为B。当turn设置为B后,就不再改变了,但busy并没有改变状态,最内层的while循环并没有退出来。所以,线程A并没有从lock()退出来。这样线程B会持续等待,等待A从lock ()中退出。反证失败。
所以,该协议不是无饥饿的。
③证明:
在这里,可能会出现:
writeA(turn = A) -> writeA(busy = true) -> readB(busy == false)
这时A就在等待turn赋值为A,B在等待busy设置为false。
和饥饿中最后的描述,就是这个问题产生的原因。
所以,该协议不是无死锁的。
习题12
证明过滤锁允许某些线程任意次数地超过其他线程。
解答:
这里先回顾一下过滤锁 等候室 的重要特性:
1.至少有一个正在尝试进入层k的线程会成功
2.如果有一个以上的线程要进入层k,则至少有一个线程被阻塞(继续在那个层等待)
再参考书中对饥饿的证明,很明显的就能看出 过滤锁允许一些线程任意次数的超过其他线程的。
习题13
双线程Peterson锁的一种改进发难就是在一棵二叉树中排列一系列双线程Peterson锁。假设n为2的幂。为每一个线程指定一个叶子锁,该锁可以由另一个线程共享。每个锁将共享自己的两个线程,视为线程0和线程1.
在树-锁请求中,线程依次获得从该线程对应的叶子直到树根的所有双线程Peterson锁。在树-锁释放中,从二叉树的根直到叶子释放该线程已获得的每个双线程Peterson锁。在任何时候,一个线程都可能被延迟一段有限的时间。(换句话说,线程可以打个盹,甚至可以放歌假,但它们始终不会死掉。)对于下述每种特性,或证明扩展锁能保持这种特性,或给出一个执行反例(可能是无限的)说明它不具备该特性。
1.互斥
2.无死锁
3.无饥饿
从一个线程开始请求树-锁直到成功获得树-锁这一时间段内,树-锁被请求和释放的次数是否存在上界?
解答:
这个题目个人没怎么看明白,所以找了别人的答案。
(引用 )
用归纳法。
假设k层的这种锁满足互斥,当树高增加到k+1层时,因为Peterson锁满足互斥,使得k+1层上的线程只能在每个k层节点上推举出一个线程,结果是构成一个k层的树,根据假设它满足互斥。
同理它也无饥饿。
满足无死锁,因为获得锁的过程是二叉树从叶子节点到跟节点的方向,整个图不能找出任一个有向环,不满足死锁的条件。
上界为n。
首先上界>=n(包括自己这一次),否则不满足互斥。
假设h=k(根节点h=0)时上界为n=(2^k),当叶节点为2n,h=k+1时,某个叶节点A可能兄弟节点赢,兄弟节点在h=k层上等2^k次加解锁;根据Peterson锁的性质,下次兄弟节点参与竞争是必然是A赢,则A在h=k层上再等待2^k次;即2^(k+1)次,必然得到锁。或者这样考虑,如果一个线程A抢锁成功,则它下一次再参与的时候必然比其它在它第二次抢锁开始之前参与的线程B(包括第一次抢锁)要后得到锁。因为叶结点为n,所以最多有n个线程在它之前。
同时,放锁之后下一次抢锁必然输给A。所以界是n。
习题14
也没怎么弄明白,参考http://blog.csdn.net/fulltopic/article/details/16835571吧
习题15
实际应用中个,几乎所有的锁请求都是无争用的,因此衡量锁性能的一种实用标准就是在没有其他线程同时请求锁的情况下,一个线程获得所需的操作步。
Cantaloupe-Melon大学的科学家设计了针对任意锁的“包装器”。同时指出,如果基本的Lock类具有互斥和无饥饿特性,那么FastPath锁也具有这两个特性,且在无争用的情况下能在常数步内获得锁。试论述为什么他们的结论是正确的,或者给出一个反例。
1 class FastPath implements Lock {
2 private static ThreadLocal myIndex;
3 private Lock lock;
4 private int x, y = -1;
5 public void lock() {
6 int i = myIndex.get();
7 x = i; // I’m here
8 while (y != -1) {} // is the lock free?
9 y = i; // me again?
10 if (x != i) // Am I still here?
11 lock.lock(); // slow path
12 }
13 public void unlock() {
14 y = -1;
15 lock.unlock();
16 }
17 }
解答:
证明就不写了,比较麻烦。可以参照书中的那几个例子。
这里lock函数没有问题,都能锁住。但是unlock()这里这样写……应该会产生无限的递归调用吧,如果栈上不溢出,应该不会出什么问题。
这里唯一的问题,我认为就在unlock()里面的lock.unlock()。(java允许这样写么? 对java的这种使用方式不是很熟悉)
这里我索性将15行忽略,这样利于自己理解。
满足互斥,但会死锁,在当进入lock.lock()的时候,再去锁的可能就不是现在的线程了,可能会是别的线程,所以,现在的这个线程一致被锁着,无法进行解锁。