1

我正在尝试在网页中实现以下目标:

  • 用户可以打开页面的多个选项卡/窗口。
  • 每隔几秒钟,我就需要其中一个选项卡/窗口来执行特定的代码部分(关键区域)。
  • 我不在乎哪个选项卡/窗口执行代码,即无需担心解决方案的公平性或饥饿属性。
  • 由于用户自己打开了选项卡/窗口,因此页面的不同实例不了解或直接引用彼此(即没有 window.parent 等)
  • 我不想需要 Flash 或 Silverlight 或其他插件,并且一切都需要在客户端运行,因此选项卡/窗口可以通信的方式非常有限(LocalStorage是迄今为止我发现的唯一一个,但可能成为其他人)。
  • 任何选项卡/窗口都可能随时崩溃或关闭或刷新,并且还可以随时打开更多选项卡/窗口,并且剩余的窗口必须“做出反应”,这样我仍然可以每次执行一次关键区域几秒钟。
  • 这需要在尽可能多的浏览器中可靠地运行,包括移动设备(caniuse评级超过 %90)。

我对解决方案的第一次尝试是使用一种简单的互斥算法,该算法使用 LocalStorage 作为共享内存。由于种种原因,我选择了 Burns 和 Lynch 在他们的论文“Mutual Exclusion Using Indivisible Reads and Writes”(第 4 页(836))中的互斥算法。

我构建了一个jsfiddle(见下面的代码)来尝试这个想法,它在 Firefox 中运行良好。如果您想尝试一下,请在 Firefox 的几个(最多 20 个)窗口中打开指向小提琴的链接,并观察其中一个窗口每秒闪烁橙色。如果您同时看到不止一个眨眼,请告诉我!:)(注意:我在小提琴中分配 ID 的方式有点俗气(简单地循环超过 0..19),只有在每个窗口都分配了不同的 ID 时,事情才会起作用。如果两个窗口显示相同的 ID,只需重新加载一个。)。

不幸的是,在 Chrome 中,尤其是在 Internet Explorer 中,事情并没有按计划工作(多个窗口闪烁)。我认为这是由于我写入 LocalStorage 的数据从一个选项卡/窗口到另一个的传播延迟(请参阅我的问题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);
4

0 回答 0