我正在尝试在网页中实现以下功能:
我首次尝试解决方案是使用一个简单的互斥算法,该算法使用LocalStorage作为共享内存。对于various reasons,我从他们的论文"Mutual Exclusion Using Indivisible Reads and Writes"(第4页(836))中选择了Burns和Lynch的互斥算法。
我构建了一个jsfiddle (see code below)来尝试这个想法,它在Firefox中运行得非常好。如果您想尝试一下,请在Firefox的几个(最多20个)窗口中打开小提琴的链接,并观看其中一个每秒闪烁橙色。如果您同时看到多个眨眼,请告诉我们! :)(注意:我在小提琴中分配ID的方式有点俗气(简单地循环超过0..19),只有每个窗口都分配了不同的ID才会有效。如果两个窗口显示相同的ID,只需重装一个。)。
不幸的是,在Chrome中,特别是在Internet Explorer中,事情无法按计划运行(多个窗口闪烁)。我认为这是由于我将写入LocalStorage的数据从一个选项卡/窗口传播到另一个选项卡/窗口的延迟(请参阅my question about this here)。
所以,基本上,我需要找到一种可以处理延迟数据的不同互斥算法(听起来很难/不可能),或者我需要找到一种完全不同的方法。也许StorageEvents可以提供帮助吗?或者可能有不同的机制不使用LocalStorage?
为了完整性,这里是小提琴的代码:
// Global constants
var LOCK_TIMEOUT = 300; // Locks time out after 300ms
var INTERVAL = 1000; // Critical section should run every second
//==================================================================================
// Assign process ID
var myID;
id = window.localStorage.getItem("id");
if (id==null) id = 0;
id = Number(id);
myID = id;
id = (id+1) % 20;
window.localStorage.setItem("id", id);
document.documentElement.innerHTML = "ID: "+myID;
//==================================================================================
// Method to indicate critical section
var lastBlink = 0;
function blink() {
col = Math.round(Math.min((new Date().getTime() - lastBlink)*2/3, 255));
document.body.style.backgroundColor = "rgb(255, "+((col >> 1)+128)+", "+col+")";
}
//==================================================================================
// Helper methods to implement expiring flags
function flagUp() {
window.localStorage.setItem("F"+myID, new Date().getTime());
}
function flagDown() {
window.localStorage.setItem("F"+myID, 0);
}
// Try to refresh flag timeout and return whether we're sure that it never expired
function refreshFlag() {
content = window.localStorage.getItem("F"+myID);
if (content==null) return false;
content = Number(content);
if ((content==NaN) || (Math.abs(new Date().getTime() - content)>=timeout))
return false;
window.localStorage.setItem("F"+myID, new Date().getTime());
return Math.abs(new Date().getTime() - content) < timeout;
}
function setFlag(key) {
window.localStorage.setItem(key, new Date().getTime());
}
function checkFlag(key, timeout) {
content = window.localStorage.getItem(key);
if (content==null) return false;
content = Number(content);
if (content==NaN) return false;
return Math.abs(new Date().getTime() - content) < timeout;
}
//==================================================================================
// Burns-Lynch mutual exclusion algorithm
var atLine7 = false;
function enterCriticalRegion() {
// Refresh flag timeout and restart algorithm if flag may have expired
if (atLine7) atLine7 &= refreshFlag();
// Check if run is due
if (checkFlag("LastRun", INTERVAL)) return false;
if (!atLine7) {
// 3: F[i] down
flagDown();
// 4: for j:=1 to i-1 do if F[j] = up goto 3
for (j=0; j<myID; j++)
if (checkFlag("F"+j, LOCK_TIMEOUT)) return false;
// 5: F[i] up
flagUp();
// 6: for j:=1 to i-1 do if F[j] = up goto 3
for (j=0; j<myID; j++)
if (checkFlag("F"+j, LOCK_TIMEOUT)) return false;
atLine7 = true;
}
// 7: for j:=i+1 to N do if F[j] = up goto 7
for (j=myID+1; j<20; j++)
if (checkFlag("F"+j, LOCK_TIMEOUT)) return false;
// Check again if run is due
return !checkFlag("LastRun", INTERVAL);
}
function leaveCriticalRegion() {
// Remember time of last succesful run
setFlag("LastRun");
// Release lock on critical region
atLine7 = false;
window.localStorage.setItem("F"+myID, 0);
}
//==================================================================================
// Keep trying to enter critical region and blink on success
function run() {
if (enterCriticalRegion()) {
lastBlink = new Date().getTime();
leaveCriticalRegion();
}
}
// Go!
window.setInterval(run, 10);
window.setInterval(blink, 10);