Art of Multiprocessor Programming 答案 ch5

该篇博客详细解答了《Art of Multiprocessor Programming》一书中第五章的多个问题,涉及并发编程中的映射策略、共识问题、一致性问题以及一致性数的讨论。通过例子和分析,探讨了在多核环境下如何处理并发操作,确保程序的正确性和可区分性。

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

47. 

如果n个线程刚好可以分成2组, 第一组的行为i与lemma5.1的行为一样,第二组的行为与Lemma5.1的行为一样,则它们有一个2值初始状态。

48. 

49. 

因为critical state必须是bivalent,所以2个后续只能 1-valent + 0-valent或者 x + bivalent。
因为if any thread moves ,the protocol has a critical state,所以后续只能是univalent。所以只能是 1-valent + 0-valent。

50.

将 n个线程分成2组,每组的线程都完全同步并行,并且行为与2线程protocol的2个线程完全一样;则如果2个线程不能decide,n个线程也不能。

51. 

将k值与2值做映射,比如

if(k >= MAXK / 2)
{
    k = 1;
}else
{
    k = 0;
}
则n线程decide on k后再映射成2值。

52.

以58题答案为例

53.

consensus >= 2:

package jokes;


import java.util.Stack;

public class StackProtocol
{
	private int[] proposes;
	static ThreadLocal<Integer> ThreadId = new ThreadLocal<Integer>()
	{
		protected Integer initialValue()
		{
			return new Integer(0);
		}
	};	
	static final int WIN = 1;
	static final int LOSE = 2;
	private Stack<Integer> stack;

	public StackProtocol()
	{
		proposes = new int[2];
		stack = new Stack<Integer>();
		stack.push(LOSE);
		stack.push(WIN);
	}
	
	private void propose(int v)
	{
		proposes[ThreadId.get()] = v;
	}
	
	public int decide(int v)
	{
		propose(v);
		if(stack.pop() == WIN)
		{
			return proposes[ThreadId.get()];
		}else
		{
			return proposes[1 - ThreadId.get()];
		}
	}
}

consensus < 3:
Both A & B pop(): 不论A先或B先,2个pop()结束后,C solo的时候无法区分谁是winner
A push & B pop: A.push() --> B.pop() 与AB都没有动作不能区分; B.pop() --> A.push() 与只有A.push()无法区分。
A push & B push: 2种顺序Stack状态一样,无法区分。

54.

如下所示:

package jokes;

import java.util.concurrent.LinkedTransferQueue;

public class QueuePeekConsensus 
{
	private LinkedTransferQueue<Integer> q;
	
	public QueuePeekConsensus(int n)
	{
		q = new LinkedTransferQueue<Integer>();
	}
	
	private void propose(int v)
	{
		q.put(v);
	}
	
	public int decide(int v)
	{
		
		propose(v);
		return q.peek();
	}
}

55.

不能。因为R(AB)只能被A和B访问,即只有2个现成参与。可以将CompareAndSet用getAndIncrement实现。则这个协议是由Common2寄存器和原子寄存器构成的,一致数为2。

一个反例:

A.CAS(RAB) --> B.CAS(RAB) --> B.CAS(RBC) --> C.CAS(RBC) --> C.CAS(RAB) --> A.CAS(RBC)

==> A-->B-->C-->A

56.

如下所示:

package jokes;

import java.util.concurrent.atomic.*;

public class CAS32Protocol 
{
	public final int THREADNUM = 3;
	public final int THREADA = 0;
	public final int THREADB = 1;
	public final int THREADC = 2;
	public final int INITIAL = -1;

	private AtomicInteger[] casObjs;
	
	private int[] proposes;
	public CAS32Protocol()
	{
		proposes = new int[THREADNUM];
		casObjs = new AtomicInteger[THREADNUM]; //ab, bc, ac
		for(int i = 0; i < THREADNUM; i ++)
		{
			casObjs[i] = new AtomicInteger(INITIAL);
		}
	}
	
	
	private synchronized boolean  propose(int index, int exp1, int exp2, int v1, int v2)
	{
		int a = index;
		int c = (index + 2) % THREADNUM;
		
		if(casObjs[a].compareAndSet(exp1, v1)
					&& casObjs[c].compareAndSet(exp2, v2))
		{
			return true;
		}
		
		return false;
	}
	
	//It dumps when champion and runner-up both halted
	public int decide(int threadId, int v)
	{
		int a = threadId;
		int b = (threadId + 1) % 3;
		int c = (threadId + 2) % 3;
		
		proposes[a] = v;
	
		if(propose(a, INITIAL, INITIAL, THREADA, THREADA))
		{
			proposes[b] = proposes[c] = proposes[a];
			//A wins
		}else if(propose(a, THREADB, INITIAL, THREADB, THREADA))
		{
			// B-->A-->C
			proposes[a] = proposes[b];
		}else if(propose(a, INITIAL, THREADC, THREADA, THREADC))
		{
			//C-->A-->B
			proposes[a] = proposes[c];
		}else
		{
			//Waiting for winner
			while(proposes[b] != proposes[c]);
			proposes[a] = proposes[b];
		}
		
		return proposes[a];
	}	
}

57. 

只要能够在loser知道自己是loser之前就能获得announce的值,就有infinite consensus

58. 

1)如下所示:

package jokes;

import java.util.concurrent.atomic.*;
public class Sticky 
{
	public enum StickyValueEnum
	{
		INIT(-1),
		ZERO(0),
		ONE(1);
		
		private int v;
		private StickyValueEnum(int v)
		{
			this.v = v;
		}
		
		public int get()
		{
			return v;
		}
	}

	private AtomicInteger v;
	
	public Sticky()
	{
		v = new AtomicInteger(StickyValueEnum.INIT.get());
	}
		
	public boolean write(StickyValueEnum v)
	{
		return this.v.compareAndSet(StickyValueEnum.INIT.get(), v.get());
	}
		
	public int read()
	{
		return v.get();
	}

}
package jokes;

import jokes.Sticky;
import jokes.Sticky.StickyValueEnum;


public class BinarySticky 
{
	private Sticky sticky;
	
	public BinarySticky()
	{
		sticky = new Sticky();
	}
	
	public int decide(StickyValueEnum v)
	{
		sticky.write(v);
		
		return sticky.read();
	}
}

2)

package jokes;

import jokes.Sticky;
import jokes.Sticky.StickyValueEnum;

//import java.nio.ByteBuffer;
import java.util.concurrent.atomic.*;
import java.util.BitSet;

public class MValentSticky
{
	private static ThreadLocal<Integer> ThreadId = new ThreadLocal<Integer>()
	{
		protected Integer initialValue()
		{
			return new Integer(0);
		}
	};
	private AtomicInteger[] proposes;
	private Sticky[] stickys;
	
	private final int stickyNum;
	private final int threadNum;
	
	public MValentSticky(int vNum,  int threadNum)
	{
		this.threadNum = threadNum;
		proposes = new AtomicInteger[this.threadNum];
		for(int i = 0; i < threadNum; i ++)
		{
			proposes[i] = new AtomicInteger(Sticky.StickyValueEnum.INIT.get());
		}
		
		stickyNum = (int)Math.ceil(Math.log(vNum) / Math.log(2));
		stickys = new Sticky[stickyNum];
		
		for(int i = 0; i < stickyNum; i ++)
		{
			stickys[i] = new Sticky();
		}
	}
	
	private static StickyValueEnum GetStickyValue(boolean bit)
	{
		if(bit)
		{
			return StickyValueEnum.ONE;
		}else
		{
			return StickyValueEnum.ZERO;
		}
	}
	
	private BitSet getBitSet(int v)
	{

		BitSet bits = new BitSet(stickyNum);
		
		int i = 0;
		while(v != 0)
		{
			if(v % 2 == 1)
			{
				bits.set(i);
			}
			i ++;
			v >>= 1;
		}
	
		return bits;
	}
	
	//maxBitIndex: first bit not matched
	private BitSet foundMatchWinner(BitSet me, int maxBitIndex)
	{
		if(maxBitIndex >= stickyNum)
		{
			return me;
		}
		
		//In same order, losers may find same winner
		//While it is not important
		for(int i = 0; i < threadNum; i ++)
		{
			if(i == ThreadId.get())
			{
				continue;
			}
						
			BitSet other = getBitSet(proposes[i].get());
			int j = 0;
			while(j < maxBitIndex && me.get(j) == other.get(j));
			
			if(j >= maxBitIndex)
			{
				return other;
			}
		}
		
		return me;
	}
	
	private int convert(BitSet bits)
	{
		int v = 0;
		for(int i = bits.length(); i >= 0; i --)
		{
			v <<= 1;
			if(bits.get(i))
			{
				v |= 1;
			}
		}
		
		return v;
	}
	
	public int decide(int proposeV)
	{
		int v = proposeV;
		int index = ThreadId.get();
		proposes[index].set(v);
		
		BitSet bits = getBitSet(v);
		
		
		for(int i = 0; i < stickyNum; i ++)
		{
			if((stickys[i].write(GetStickyValue(bits.get(i)))))
			{
				continue;
			}else
			{
				bits = foundMatchWinner(bits, i);
				//Not necessary to update proposes[] 
				//as candidates can get value from sticky
			}
		}
		
		return convert(bits);
	}
	
	public static void main(String[] args)
	{
		MValentSticky toy = new MValentSticky(4, 2);
		int rc = toy.decide(3);
		System.out.println("End of test " + rc);
	}
}

59.

一个用原子寄存器实现的SetAgree(2)如下所示。因为它可以用AtomicRegister实现,所以一致数为1。
但是当 k < n (线程数), consensus number = infinite。

package jokes;

import java.util.concurrent.atomic.*;

public class SetAgree2 
{
	private AtomicBoolean[] proposes;
	
	public final int ThreadNum;
	
	public SetAgree2(int threadNum)
	{
		this.ThreadNum = threadNum;
		proposes = new AtomicBoolean[this.ThreadNum];
		for(int i = 0; i < this.ThreadNum; i ++)
		{
			proposes[i] = new AtomicBoolean();
		}
	}
	
	public void propose(boolean v, int threadId)
	{
		if(threadId < ThreadNum && threadId >= 0)
		{
			proposes[threadId].set(v);
		}
	}
	
	public boolean decide(int threadId)
	{
		if(threadId < ThreadNum && threadId >= 0)
		{
			return proposes[threadId].get();
		}
		
		return false;
	}
	
}

60.

1

61

A类:不能。将收到消息看成对一个对象进行读操作的返回。因为这个对象之提供读和写操作,即相当于原子寄存器。
则A类型的实现相当于对一组原子寄存器进行读写实现的,所以consensus number =1
B类:类似于访问一个提供peek方法的queue,consensus number = infinite

62.

如果A.propose == 1, A decides 1; B可以随意决定都是正确的。
如果A.propose == 0 而且B还没有propose,则A可以决定一个合理的值,B 在决定的时候因为可以看到A,所以可以配合这个决定。
因为如果B后来propose了0则A必须决定0,所以这种情况下A决0。
如果A decides时候B已经有了propose,即B决定在A之前。如果B.propose == 0,则A必须决定0,否则A必须决定1,因为B已经决定了1。

package jokes;

public class QuasiConsensus 
{
	public static final int THREADNUM = 2;
	
	int[] proposes;
	
	public QuasiConsensus()
	{
		proposes = new int[THREADNUM];
		for(int i = 0; i < THREADNUM;i ++)
		{
			proposes[i] = -1;
		}
	}
	
	//threadId: A--> true, B-->false
	public int decide(boolean threadId, int v)
	{
		if(threadId) // thread A
		{
			proposes[1] = v;
			
			if(v == 1)
			{
				return 1;
			}else
			{
				if(proposes[0] != 1)
				{
					return 0;
				}else
				{
					return 1;
				}
			}
		}
		else // thread B
		{
			proposes[0] = v;
			
			if(v == 0)
			{
				return 0;
			}else
			{
				if(proposes[1] != 0)
				{
					return 1;
				}else
				{
					return 0;
				}
			}
		}
	}
}

63. 

因为一致对象已经有决定的能力,即只要输入状态时合法的,一致性就是可能的。

64. 

同48题?

65. 

package jokes;

import java.util.concurrent.atomic.*;

import jokes.NoDefineException;

public class TeamConsensusObject 
{
	private AtomicInteger[] proposes;
	public final static int INIT = -1;
	
	public TeamConsensusObject()
	{
		proposes = new AtomicInteger[2];
		for(int i = 0; i < proposes.length; i ++)
		{
			proposes[i] = new AtomicInteger(INIT);
		}
	}
	
	public void propose(int v)
	{
		if(!proposes[0].compareAndSet(INIT, v))
		{
			proposes[1].compareAndSet(INIT, v);
		}
	}
	
	public int decide(int v) throws NoDefineException
	{
		if(v == proposes[0].get())
		{
			return v;
		}else if(v == proposes[1].get())
		{
			return proposes[0].get();
		}else
		{
			throw new NoDefineException("");
		}
	}
}
package jokes;

import jokes.NoDefineException;
import jokes.TeamConsensusObject;

public class TeamConsensusProtocol 
{
	private TeamConsensusObject[] nodes;
	private int threadNum;
	
	public TeamConsensusProtocol(int threadNum)
	{
		this.threadNum =  (int) Math.pow(2, (Math.ceil((Math.log(threadNum)/ Math.log(2)))));
		System.out.println("Get threadNum " + this.threadNum);
		nodes = new TeamConsensusObject[this.threadNum * 2];
		for(int i = 0; i < nodes.length; i ++)
		{
			nodes[i] = new TeamConsensusObject();
		}
	}
	
	public int decide(int threadId, int proposeV) throws NoDefineException
	{
		if(threadId >= this.threadNum || threadId < 0)
		{
			throw new NoDefineException("");
		}
		
		int nodeId = threadId;
		int v = proposeV;
		int step = this.threadNum;
		int base = 0;
		
		while(step > 0)
		{
			nodes[nodeId].propose(v);
			v = nodes[nodeId].decide(v);
		
			threadId = (threadId + 1) / 2;
			base += step;
			nodeId = base + threadId;
			step /= 2;
		}
		
		return v;
	}
	
	public static void main(String[] args)
	{
		TeamConsensusProtocol tester = new TeamConsensusProtocol(7);
		
		try
		{
			System.out.println("Decide " + tester.decide(6, 5));
		}catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}

66. 

58题解法,将值换成thread id

67. 

如果一个算法的执行的步数是有限的,则无锁和无等待是等价的。对于一个给定的一致性对象,既然它的decide()方法只被每个线程执行一次,则它的无锁实现也是无等待的,反之亦然。
所以没有原子寄存器的无等待实现也就没有它的无锁实现。

68. 

只用原子寄存器实现的scan实现的peek()操作的一致数为1。一旦有deq被调用,peek()返回的在deq调用前和调用后的得到的结果是不一样的,不满足一致的条件。
如果没有线程调用dep,因为enq不是一个原子操作,虽然getAndIncrement()的一致数为2,但是items[slot]=x是一个原子寄存器的操作,一致数只有1。所以即使不用deq实现的protocol一致数也是1。

69.

package jokes;

import java.util.concurrent.atomic.*;

public class NewCASObj 
{
	private AtomicInteger v;
	
	public NewCASObj(int v)
	{
		this.v = new AtomicInteger(v);
	}
	
	public int newCompareAndSet(int exp, int replace)
	{
		v.compareAndSet(exp, replace);
		do
		{
			exp = v.get();
		}while(!v.compareAndSet(exp, exp));
		return exp;
	}
}

70. 

根据5.8.1.的证明,n界CAS的一致数>=n。如果有n+1线程第n+1次调用这个对象,返回值为⊥,则前n次调用的任何状态都不能被区分,n+k (k > 0)次调用都不能得到winner的信息。

71. 

package jokes;

import java.util.concurrent.atomic.*;

public class CASAssign32 
{
	AtomicInteger[] rs;
	
	public CASAssign32(int initValue)
	{
		rs = new AtomicInteger[3];
		for(int i = 0; i < rs.length; i ++)
		{
			rs[i] = new AtomicInteger(initValue);
		}
	}
	
	//Assign in by increasing index
	public void assign(int i0, int i1, int v0, int v1)
	{
		if(i0 >= 3 || i1 >= 3 || i0 < 0 || i1 < 0 || i0 == i1)
		{
			return;
		}
		
		if(i0 > i1)
		{
			int tmp = i0;
			i0 = i1;
			i1 = tmp;
		}
		
		while(true)
		{
			int origV0 = rs[i0].get();
			int origV1 = rs[i1].get();
			
			if(rs[i0].compareAndSet(origV0, v0))
			{
				if(rs[i1].compareAndSet(origV1, v1))
				{
					return;
				}
			}
		}
	}
	
	/*
	 * Check by decreasing index.
	 * For example, when rs2 modified in read rs0 in assign(1, 2, x, x)
	 * The returned value is valid as r1, r2 had been checked
	 */
	public int read(int i) throws NoDefineException
	{
		if(i >= 3 || i < 0)
		{
			throw new NoDefineException("");
		}
		
		while(true)
		{
			int v0 = rs[0].get();
			int v1 = rs[1].get();
			int v2 = rs[2].get();
			
			if(rs[2].compareAndSet(v2, v2))
				if(rs[1].compareAndSet(v1, v1))
					if(rs[0].compareAndSet(v0, v0))
					{
						return rs[i].get();
					}
		}
	}
}

72. 

73. 

package jokes;

import java.util.concurrent.atomic.*;

public class CASConsensus 
{
	private AtomicInteger proposeId;
	private int[] proposes;
	private int threadNum;
	
	public CASConsensus(int threadNum)
	{
		this.threadNum = threadNum;
		proposes = new int[this.threadNum];
		proposeId = new AtomicInteger(-1);
	}
	
	public int decide(int threadId, int v)
	{
		proposes[threadId] = v;
		
		if(proposeId.compareAndSet(-1, threadId))
		{
			return v;
		}else
		{
			for(int i = 0; i < threadNum; i ++)
			{
				//Suppose 0 is invalid/initial value
				if(proposes[i] != 0)
				{
					if(proposeId.compareAndSet(i, i))
					{
						return proposes[i];
					}
				}
			}
		}
		//Make compiler happy
		return v;
	}
}

74.

如果2个线程input不一样,调度器每当探测到其中一个线程(线程i)flip() == true时,在i读到prefer[j]之后写prefer[i]之前停下线程i;线程j solo直到线程j flip() == true,j读到prefer[i]之后写prefer[j]之前,启动线程i;即2个线程同时换到对方的prefer值。这样decide将没有返回。

75.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值