国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 開(kāi)發(fā) > AJAX > 正文

揭秘在AJAX程序中實(shí)現(xiàn)互斥

2024-09-01 08:26:30
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

 隨著AJAX范例得到越來(lái)越廣泛的應(yīng)用,瀏覽器頁(yè)面可以在向后臺(tái)服務(wù)器請(qǐng)求數(shù)據(jù)的同時(shí)保持前端用戶界面的活躍性(因此在AJAX中稱為異步)。然而,當(dāng)這兩個(gè)活動(dòng)同時(shí)訪問(wèn)共用的JavaScript和DOM數(shù)據(jù)結(jié)構(gòu)時(shí)就會(huì)引發(fā)問(wèn)題。JavaScript沒(méi)有提供針對(duì)該并發(fā)程序問(wèn)題的經(jīng)典解決方案。本文描述了作者在互斥機(jī)制方面的新見(jiàn)解,該經(jīng)過(guò)驗(yàn)證的互斥機(jī)制在JavaScript中能發(fā)揮良好的作用。

  為什么需要互斥?

  當(dāng)多個(gè)程序邏輯線程同時(shí)訪問(wèn)相同數(shù)據(jù)的時(shí)候,問(wèn)題便產(chǎn)生了。程序通常假定與其交互的數(shù)據(jù)在交互過(guò)程中不發(fā)生改變。訪問(wèn)這些共享數(shù)據(jù)結(jié)構(gòu)的代碼稱為臨界區(qū),一次只允許一個(gè)程序訪問(wèn)的機(jī)制被稱為互斥。在AJAX應(yīng)用程序中,當(dāng)對(duì)來(lái)自XMLHttpRequest的應(yīng)答進(jìn)行異步處理的代碼同時(shí)操縱正在被用戶界面使用的數(shù)據(jù)時(shí),便會(huì)發(fā)生這種情況。這個(gè)共用的數(shù)據(jù)可能是用于實(shí)現(xiàn)MVC數(shù)據(jù)模型的JavaScript和/或web頁(yè)面自身的DOM。如果二者中的任一個(gè)對(duì)共享數(shù)據(jù)做了不協(xié)調(diào)的更改,那么二者的邏輯都將中斷。

  也許您會(huì)說(shuō)“等等,為什么我沒(méi)有遇到過(guò)這種問(wèn)題?”。遺憾的是,這種問(wèn)題是同步依賴的(也叫做競(jìng)態(tài)條件),因此它們并不總是發(fā)生,或者也許從不發(fā)生。它們的或然性基于許多因素?;诮研钥紤],富internet應(yīng)用程序應(yīng)該通過(guò)確保這些問(wèn)題不會(huì)發(fā)生來(lái)阻止出現(xiàn)這種情況。

  因此,需要一種互斥機(jī)制來(lái)確保同時(shí)只能打開(kāi)一個(gè)臨界區(qū),并且在它結(jié)束之后才能打開(kāi)另一個(gè)。在大多數(shù)主流計(jì)算機(jī)語(yǔ)言和執(zhí)行框架中,都提供互斥機(jī)制(經(jīng)常是幾種),但是應(yīng)用于瀏覽器端的JavaScript卻沒(méi)有提供這種互斥機(jī)制。雖然存在一些無(wú)需專門(mén)的語(yǔ)言或環(huán)境支持的經(jīng)典互斥實(shí)現(xiàn)算法,但是即使這樣還是需要一些JavaScript和瀏覽器(如Internet Explorer)所缺少的要素。接下來(lái)介紹的經(jīng)典算法在這些瀏覽器和語(yǔ)言中能發(fā)揮良好的作用。

  面包店算法

  在計(jì)算機(jī)科學(xué)文獻(xiàn)中的幾種互斥算法中,所謂的Lamport面包店算法可以有效地用于多個(gè)相互競(jìng)爭(zhēng)的控制線程,該算法中線程之間的通信只能在共享內(nèi)存中進(jìn)行(即,不需要諸如信號(hào)量、原子性的set-and-test之類的專門(mén)機(jī)制)。該算法的基本思想源于面包店,因?yàn)槊姘晷枰热√?hào)然后等候叫號(hào)。清單1給出了該算法的框架(引自Wikipedia),該算法可以使各線程進(jìn)出臨界區(qū)而不產(chǎn)生沖突。

  清單1. Lamport面包店算法偽代碼

 

// declaration & initial values of global variables
Enter, Number: array [1..N] of integer = {0};

// logic used by each thread...
// where "(a, b) < (c, d)"
// means "(a < c) or ((a == c) and (b < d))"
Thread(i) {
 while (true) {
  Enter [i] = 1;
  Number[i] = 1 + max(Number[1],...,Number[N]);
  Enter [i] = 0;
  for (j=1; j<=N; ++j) {
   while (Enter[j] != 0) {
    // wait until thread j receives its number
   }
   while ((Number[j]!=0) && ((Number[j],j) < (Number[i],i))) {
    // wait until threads with smaller numbers
    // or with the same number, but with higher
    // priority, finish their work
   }
  }
  // critical section...
  Number[i] = 0;
  // non-critical section...
 }
}


  如上所示,該算法假定各線程清楚自己的線程編號(hào)(常量i)和當(dāng)前正在活動(dòng)的線程總數(shù)(常量N)。此外,還假定存在一種等待或休眠方式,例如:暫時(shí)將CPU釋放給其他線程。遺憾的是,Internet Explorer中的JavaScript沒(méi)有這種能力。雖然如此,如果實(shí)際運(yùn)行在同一線程上的多個(gè)代碼部分表現(xiàn)為各自運(yùn)行在獨(dú)立的虛擬線程上,那么該面包店算法不會(huì)中斷。同樣,JavaScript具有一種在指定延遲后調(diào)度函數(shù)的機(jī)制,所以,可以使用下面的這些方法來(lái)優(yōu)化面包店算法。


  Wallace變體

  在JavaScript中實(shí)現(xiàn)Lamport面包店算法的主要障礙在于缺少線程API。無(wú)法確定當(dāng)前正在哪個(gè)線程上運(yùn)行以及當(dāng)前正在活動(dòng)的線程數(shù)目,也無(wú)法將CPU釋放給其他的線程,無(wú)法創(chuàng)建新的線程來(lái)管理其他線程。因此,無(wú)法查證如何將特定的瀏覽器事件(例如:?jiǎn)螕舭醇~、可用的XML應(yīng)答等)分配到線程。

  克服這些障礙的一種方法是使用Command設(shè)計(jì)模式。通過(guò)將所有應(yīng)該進(jìn)入臨界區(qū)的邏輯以及所有啟動(dòng)該邏輯所需的數(shù)據(jù)一起放入到command 對(duì)象中,可以在負(fù)責(zé)管理command的類中重寫(xiě)面包店算法。該互斥類僅在沒(méi)有其他臨界區(qū)(封裝為獨(dú)立的command對(duì)象方法)在執(zhí)行時(shí)調(diào)用臨界區(qū),就像它們各自運(yùn)行在不同的虛擬線程中一樣。JavaScript的setTimeout()機(jī)制用于將CPU釋放給其他正在等待的command。

  為command對(duì)象假定一個(gè)簡(jiǎn)單的基類(見(jiàn)清單2中的Command),可以定義一個(gè)類(見(jiàn)清單3中的Mutex)來(lái)實(shí)現(xiàn)面包店算法的Wallace變體。注意,雖然可以通過(guò)很多方式在JavaScript中實(shí)現(xiàn)基類對(duì)象(為了簡(jiǎn)潔起見(jiàn),這里使用一種簡(jiǎn)單的方式),但是只要各個(gè)command對(duì)象擁有某個(gè)惟一的id,而且整個(gè)臨界區(qū)被封裝在單獨(dú)的方法中,那么任何對(duì)象模式都可以使用這種方法。

  清單2. 用于 Command 對(duì)象的簡(jiǎn)單基類

 

1 function 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 }


  Command類演示了三個(gè)臨界區(qū)方法(見(jiàn)5-7行),但是只要預(yù)先將對(duì)該方法的調(diào)用封裝在Mutex中(見(jiàn)9-11行),那么就可以使用任何方法。有必要認(rèn)識(shí)到,常規(guī)方法調(diào)用(例如非同步的方法調(diào)用)與同步方法調(diào)用之間存在著重要的區(qū)別:具有諷刺意味的是,必須保證同步方法不同步運(yùn)行。換句話說(shuō),當(dāng)調(diào)用sDoIt()方法時(shí),必須確保方法doit()還未運(yùn)行,即使方法sDoIt()已經(jīng)返回。doit()方法可能已結(jié)束,或者直到將來(lái)的某一時(shí)間才開(kāi)始執(zhí)行。也就是說(shuō),將對(duì)Mutex的實(shí)例化視為啟動(dòng)一個(gè)新的線程。

  清單3.作為類 Mutex實(shí)現(xiàn)的 Wallace 變體

 

1 function 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
11      || (j.number && (j.number < this.number ||
12      (j.number == this.number
13      && j.c.id < this.c.id))))
14      return setTimeout
15      ("Mutex.SLICE("+this.c.id+","+j.c.id+")",10);
16     }
17     //run with exclusive access
18     this.c[ this.methodID ]();
19     //release exclusive access
20     this.number = 0;
21     Mutex.Wait.remove( this.c.id );
22    }
23    // constructor logic
24    this.c = cmdObject;
25    this.methodID = methodName;
26    //(enter and number are "false" here)
27    Mutex.Wait.add( this.c.id, this );
28    this.enter = true;
29    this.number = (new Date()).getTime();
30    this.enter = false;
31   this.attempt( Mutex.Wait.first() );
32  }


  Mutex類的基本邏輯是將每個(gè)新的Mutex實(shí)例放入主等待清單,然后將其在等待隊(duì)列中啟動(dòng)。因?yàn)槊看蔚竭_(dá)“隊(duì)首”的嘗試都需要等待(除了最后一次),所以使用setTimeout來(lái)調(diào)度每次在當(dāng)前嘗試停止的位置啟動(dòng)的新嘗試。到達(dá)隊(duì)首時(shí)(見(jiàn)17行),便實(shí)現(xiàn)了互斥性訪問(wèn);因此,可以調(diào)用臨界區(qū)方法。執(zhí)行完臨界區(qū)后,釋放互斥性訪問(wèn)并從等待清單中移除Mutex實(shí)例(見(jiàn)20-21行)。

  Mutex構(gòu)造函數(shù)(見(jiàn)23-31行)記錄其Command對(duì)象和方法名參數(shù),然后寄存在一個(gè)運(yùn)行中臨界區(qū)的稀疏數(shù)組中(Mutex.Wait),這通過(guò)清單4中所示的Map類來(lái)實(shí)現(xiàn)。然后構(gòu)造函數(shù)獲得下一個(gè)編號(hào),并在隊(duì)尾開(kāi)始排隊(duì)。由于等待編號(hào)中的間隔或副本不存在問(wèn)題,所以實(shí)際上使用當(dāng)前的時(shí)間戳作為下一個(gè)編號(hào)。

  attempt()方法將初始偽代碼中的兩個(gè)wait循環(huán)組合成一個(gè)單獨(dú)的循環(huán),該循環(huán)直到隊(duì)首時(shí)才對(duì)臨界區(qū)失效。該循環(huán)是一種忙碌-等待循環(huán)檢測(cè)方式,可以通過(guò)在setTimeout()調(diào)用中指定延遲量來(lái)終止該循環(huán)。由于setTimeout需要調(diào)用“無(wú)格式函數(shù)”,所以在第4-6行定義了靜態(tài)幫助器方法(Mutex.SLICE)。SLICE在主等待清單中查找指定的Mutex對(duì)象,然后調(diào)用其attempt()方法,用start參數(shù)指定到目前為止其所獲得的等待清單的長(zhǎng)度。每次SLICE()調(diào)用都像獲得了“一塊CPU”。這種(通過(guò)setTimeout)適時(shí)釋放CPU的協(xié)作方式令人想到協(xié)同程序。

  清單4. 作為 Map數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的稀疏數(shù)組

 

function Map() {
 this.map = new Object();
 // Map API
 this.add = function( k,o ){
  this.map[k] = o;
 }
 this.remove = function( k ){
  delete this.map[k];
 }
 this.get = function( k ){
  return k==null ? null : this.map[k];
 }
 this.first = function(){
  return this.get( this.nextKey() );
 }
 this.next = function( k ){
  return this.get( this.nextKey(k) );
 }
 this.nextKey = function( k ){
  for (i in this.map) {
   if ( !k ) return i;
   if (k==i) k=null; /*tricky*/
  }
  return null;
 }
}

 

  富Internet應(yīng)用程序集成

  由于Mutex所處理的線程(虛擬的或者非虛擬的)數(shù)量是動(dòng)態(tài)變化的,所以可以確定一個(gè)基本事實(shí):無(wú)法通過(guò)像瀏覽器為各個(gè)瀏覽器事件分配單獨(dú)的線程那樣的方式來(lái)獲得線程標(biāo)識(shí)符。這里做了一個(gè)類似的假定,那就是每個(gè)完整的事件處理程序組成一個(gè)完整的臨界區(qū)?;谶@些假定,每個(gè)事件處理函數(shù)都可以轉(zhuǎn)變成一個(gè)command對(duì)象,并使用Mutex對(duì)其進(jìn)行管理。當(dāng)然,如果未將代碼明確組織成事件處理函數(shù),那么將需要重構(gòu)。換句話說(shuō),不是直接在HTML事件屬性中進(jìn)行邏輯編碼(例如:onclick='++var'),而是調(diào)用事件處理函數(shù)(例如:onclick='FOO()'和function FOO(){++var;})。

  清單5. 使用了非同步事件處理程序的示例web頁(yè)面

 

<html>
<script language="JavaScript">
function newState(){
 if (XMLreq.readyState==4) processReply();
}
function requestData(){
 ...set up asynchronous XML request...
 XMLreq.onreadystatechange = newState;
 ...launch XML request...
}
function processReply(){
 var transformedData = ...process data to HTML...
 OutputArea.innerHTML = transformedData + "<br>";
}
function clearArea(){
 OutputArea.innerHTML = "cleared<br>";
}
</script>
<body onload="requestData();">
?。糹nput type="button" value="clear" onclick="clearArea()">
 <div id="OutputArea"/>
</body>
</html>


  例如,假設(shè)有三個(gè)事件處理程序函數(shù),它們操縱清單5所示的共用數(shù)據(jù)。它們處理頁(yè)面加載事件、單擊按鈕事件和來(lái)自XML請(qǐng)求的應(yīng)答事件。頁(yè)面加載事件發(fā)出某個(gè)異步請(qǐng)求來(lái)要求獲取數(shù)據(jù)并指定請(qǐng)求-應(yīng)答事件處理程序,該處理程序處理接收到的數(shù)據(jù),并將其加載到共用數(shù)據(jù)結(jié)構(gòu)。單擊按鈕事件處理程序也影響共用數(shù)據(jù)結(jié)構(gòu)。為了避免這些事件處理程序發(fā)生沖突,可以通過(guò)清單6所示的Mutex將它們轉(zhuǎn)變成command并加以調(diào)用(假設(shè)JavaScript include文件mutex.js中包含Map和Mutex)。注意,雖然可以使用優(yōu)美的類繼承機(jī)制來(lái)實(shí)現(xiàn)Command子類,但是該代碼說(shuō)明了最簡(jiǎn)單的方法,該方法僅需要全局變量NEXT_CMD_ID。

  清單6. 轉(zhuǎn)化為同步事件處理程序的web頁(yè)面

 

<html>
<script src="mutex.js"></script>
<script language="JavaScript">
function requestData (){
 new Mutex(new RequestDataCmd(),"go"); }
 function processReply(){
  new Mutex(new ProcessReplyCmd(),"go"); }
 function clearArea (){
  new Mutex(new ClearAreaCmd(),"go"); }
 function newState (){
  if (XMLreq.readyState==4) processReply(); }

 var NEXT_CMD_ID = 0;

 function RequestDataCmd(){
  this.id = ++NEXT_CMD_ID;
  this.go = function(){
   ...set up asynchronous XML request...
   XMLreq.onreadystatechange = NewState;
   ...launch XML request...
  }
 }
 function ProcessReplyCmd(){
  this.id = ++NEXT_CMD_ID;
  this.go = function(){
   var transformedData = ...process data to HTML...
   OutputArea.innerHTML = transformedData + "<br>";
  }
 }
 function ClearAreaCmd(){
  this.id = ++NEXT_CMD_ID;
  this.go = function(){
   OutputArea.innerHTML = "cleared<br>"; }
 }
</script>
<body onload="requestData();">
<input type="button" value="clear" onclick="clearArea()">
<div id="OutputArea"/>
</body>
</html>


  已經(jīng)通過(guò)Mutex將這三個(gè)事件處理程序函數(shù)轉(zhuǎn)變?yōu)檎{(diào)用它們的初始邏輯(當(dāng)前都被預(yù)包裝于command類中)。各個(gè)command類定義一個(gè)獨(dú)特的標(biāo)識(shí)符和一個(gè)包含臨界區(qū)邏輯的方法,從而滿足了command接口的要求。

  結(jié)束語(yǔ)

  借助于AJAX和RIA,構(gòu)建復(fù)雜的動(dòng)態(tài)用戶界面的推動(dòng)力正在促使開(kāi)發(fā)人員使用先前與胖GUI客戶端緊密聯(lián)系的設(shè)計(jì)模式(例如:模型-視圖-控制器)。隨著視圖和控制器的定義模塊化,且每一個(gè)都帶有自己的事件和事件處理程序(除了共用數(shù)據(jù)模型),發(fā)生沖突的機(jī)率成倍提高。通過(guò)把事件處理邏輯封裝到Command類中,不僅可以使用Wallace變體,而且為提供豐富的撤消/重做功能、腳本編寫(xiě)界面和單元測(cè)試工具創(chuàng)造了條件。

 

 

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 长乐市| 乾安县| 麻城市| 罗平县| 察哈| 张家界市| 墨玉县| 平和县| 五台县| 丘北县| 通榆县| 辽中县| 漳浦县| 突泉县| 焦作市| 景洪市| 台东县| 巴青县| 界首市| 长垣县| 隆安县| 新邵县| 瑞安市| 龙山县| 紫云| 达孜县| 九江县| 盐边县| 瓦房店市| 宁陕县| 金华市| 达日县| 古田县| 岢岚县| 葵青区| 庆城县| 犍为县| 鄂托克前旗| 东源县| 松原市| 电白县|