为AJAX实现互斥
Wallace变体
在JavaScript中实现Lamport面包店算法的主要障碍在于缺少线程API。无法确定当前正在哪个线程上运行以及当前正在活动的线程数目,也无法将CPU释放给其他的线程,无法创建新的线程来管理其他线程。因此,无法查证如何将特定的浏览器事件(例如:单击按纽、可用的XML应答等)分配到线程。
克服这些障碍的一种方法是使用Command设计模式。通过将所有应该进入临界区的逻辑以及所有启动该逻辑所需的数据一起放入到command 对象中,可以在负责管理command的类中重写面包店算法。该互斥类仅在没有其他临界区(封装为独立的command对象方法)在执行时调用临界区,就像它们各自运行在不同的虚拟线程中一样。JavaScript的setTimeout()机制用于将CPU释放给其他正在等待的command。
为command对象假定一个简单的基类(见清单2中的Command),可以定义一个类(见清单3中的Mutex)来实现面包店算法的Wallace变体。注意,虽然可以通过很多方式在JavaScript中实现基类对象(为了简洁起见,这里使用一种简单的方式),但是只要各个command对象拥有某个惟一的id,而且整个临界区被封装在单独的方法中,那么任何对象模式都可以使用这种方法。
清单2. 用于 Command 对象的简单基类
1function Command() ...{ 2
if (!Command.NextID) Command.NextID = 0; 3
this.id = ++Command.NextID; 4
// unsynchronized API 5
this.doit = function()...{ alert("DOIT called"); } 6
this.undo = function()...{ alert("UNDO called"); } 7
this.redo = function()...{ this.doit(); } 8
// synchronized API 9
this.sDoIt = function()...{ new Mutex(this,"doit"); } 10
this.sUnDo = function()...{ new Mutex(this,"undo"); } 11
this.sReDo = function()...{ new Mutex(this,"redo"); } 12
} 13
Command类演示了三个临界区方法(见5-7行),但是只要预先将对该方法的调用封装在Mutex中(见9-11行),那么就可以使用任何方法。有必要认识到,常规方法调用(例如非同步的方法调用)与同步方法调用之间存在着重要的区别:具有讽刺意味的是,必须保证同步方法不同步运行。换句话说,当调用sDoIt()方法时,必须确保方法doit()还未运行,即使方法sDoIt()已经返回。doit()方法可能已结束,或者直到将来的某一时间才开始执行。也就是说,将对Mutex的实例化视为启动一个新的线程。
清单3.作为类 Mutex实现的 Wallace 变体
1function Mutex( cmdObject, methodName ) ...{ 2
// define static field and method 3
if (!Mutex.Wait) Mutex.Wait = new Map(); 4
Mutex.SLICE = function( cmdID, startID ) ...{ 5
Mutex.Wait.get(cmdID).attempt( Mutex.Wait.get(startID) ); 6
} 7
// define instance method 8
this.attempt = function( start ) ...{ 9
for (var j=start; j; j=Mutex.Wait.next(j.c.id)) ...{ 10
if (j.enter || (j.number && (j.number < this.number||(j.number == this.number 11
&& j.c.id < this.c.id)))) 12
return setTimeout 13
("Mutex.SLICE("+this.c.id+","+j.c.id+")",10); 14
} 15
//run with exclusive access 16
this.c[ this.methodID ](); 17
//release exclusive access 18
this.number = 0; 19
Mutex.Wait.remove( this.c.id ); 20
} 21
// constructor logic 22
this.c = cmdObject; 23
this.methodID = methodName; 24
//(enter and number are "false" here) 25
Mutex.Wait.add( this.c.id, this ); 26
this.enter = true; 27
this.number = (new Date()).getTime(); 28
this.enter = false; 29
this.attempt( Mutex.Wait.first() ); 30
}
Mutex类的基本逻辑是将每个新的Mutex实例放入主等待清单,然后将其在等待队列中启动。因为每次到达“队首”的尝试都需要等待(除了最后一次),所以使用setTimeout来调度每次在当前尝试停止的位置启动的新尝试。到达队首时(见17行),便实现了互斥性访问;因此,可以调用临界区方法。执行完临界区后,释放互斥性访问并从等待清单中移除Mutex实例(见20-21行)。
