localStorage线程安全吗?

Tom*_*ica 21 javascript multithreading local-storage

我很好奇localStorage通过在两个浏览器标签中同时覆盖它来破坏输入的可能性.我应该为本地存储创建互斥吗?
我已经在考虑这样的伪类:

LocalStorageMan.prototype.v = LocalStorageMan.prototype.value = function(name, val) {
  //Set inner value
  this.data[name] = val;
  //Delay any changes if the local storage is being changed
  if(localStorage[this.name+"__mutex"]==1) {
    setTimeout(function() {this.v(name, val);}, 1);
    return null;  //Very good point @Lightness Races in Orbit 
  }
  //Lock the mutext to prevent overwriting
  localStorage[this.name+"__mutex"] = 1;
  //Save serialized data
  localStorage[this.name] = this.serializeData;
  //Allow usage from another tabs
  localStorage[this.name+"__mutex"] = 0;
}
Run Code Online (Sandbox Code Playgroud)

上述功能意味着本地存储管理器正在管理本地存储的一个特定密钥 - localStorage["test"]例如.我想将此用于greasomonkey用户脚本,其中避免冲突是一个优先事项.

Ben*_*aum 31

是的,它是线程安全的.但是,你的代码不是原子的,那就是你的问题.我会localStorage先解决安全问题,但首先是如何解决问题.

两个选项卡都可以将if检查一起传递并写入相互覆盖的项目.处理这个问题的正确方法是使用StorageEvents.

这些允许您在localStorage中更改密钥时通知其他窗口,在内置消息中以安全的方式有效地解决问题.这是一个关于他们的好读物.我们举个例子:

// tab 1
localStorage.setItem("Foo","Bar");

// tab 2
window.addEventListener("storage",function(e){
    alert("StorageChanged!"); // this will run when the localStorage is changed
});
Run Code Online (Sandbox Code Playgroud)

现在,我承诺的线程安全:)

我喜欢 - 让我们从两个角度来看 - 从规范和使用实现.

规范

让我们通过规范显示它的线程安全.

如果我们检查Web存储规范,我们可以看到它特别注意到:

由于使用了存储互斥锁,多个浏览上下文将能够以脚本无法检测到任何并发脚本执行的方式同时访问本地存储区域.

因此,存储对象的length属性以及该对象的各种属性的值在脚本执行时不能更改,而不是以脚本本身可预测的方式.

它甚至进一步阐述:

每当要检查,返回,设置或删除localStorage属性Storage对象的属性时,无论是作为直接属性访问的一部分,还是在检查属性的存在时,在属性枚举期间,在确定存在的属性数时,或者作为执行Storage接口上定义的任何方法或属性的一部分,用户代理必须首先获取存储互斥锁.

强调我的.它还指出,一些实现者并不喜欢这样的说明.

在实践中

让我们在实现中展示它的线程安全性.

选择一个随机浏览器,我选择了WebKit(因为我之前不知道那个代码在哪里).如果我们查看WebKit的Storage实现,我们可以看到它有互斥量的票价.

让我们从一开始就采取它.当你打电话setItem或分配时,会发生这种情况:

void Storage::setItem(const String& key, const String& value, ExceptionCode& ec)
{
    if (!m_storageArea->canAccessStorage(m_frame)) {
        ec = SECURITY_ERR;
        return;
    }

    if (isDisabledByPrivateBrowsing()) {
        ec = QUOTA_EXCEEDED_ERR;
        return;
    }

    bool quotaException = false;
    m_storageArea->setItem(m_frame, key, value, quotaException);

    if (quotaException)
        ec = QUOTA_EXCEEDED_ERR;
}
Run Code Online (Sandbox Code Playgroud)

接下来,发生在StorageArea:

void StorageAreaImpl::setItem(Frame* sourceFrame, const String& key, const String& value, bool& quotaException)
{
    ASSERT(!m_isShutdown);
    ASSERT(!value.isNull());
    blockUntilImportComplete();

    String oldValue;
    RefPtr<StorageMap> newMap = m_storageMap->setItem(key, value, oldValue, quotaException);
    if (newMap)
        m_storageMap = newMap.release();

    if (quotaException)
        return;

    if (oldValue == value)
        return;

    if (m_storageAreaSync)
        m_storageAreaSync->scheduleItemForSync(key, value);

    dispatchStorageEvent(key, oldValue, value, sourceFrame);
}
Run Code Online (Sandbox Code Playgroud)

请注意blockUntilImportComplete这里.我们来看看:

void StorageAreaSync::blockUntilImportComplete()
{
    ASSERT(isMainThread());

    // Fast path.  We set m_storageArea to 0 only after m_importComplete being true.
    if (!m_storageArea)
        return;

    MutexLocker locker(m_importLock);
    while (!m_importComplete)
        m_importCondition.wait(m_importLock);
    m_storageArea = 0;
}
Run Code Online (Sandbox Code Playgroud)

他们还添加了一个很好的说明:

// FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so).
// Blocking everything until the import is complete is by far the simplest and safest thing to do, but
// there is certainly room for safe optimization: Key/length will never be able to make use of such an
// optimization (since the order of iteration can change as items are being added). Get can return any
// item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list
// of items the import should not overwrite. Clear can also work, but it'll need to kill the import
// job first.
Run Code Online (Sandbox Code Playgroud)

解释这个工作,但它可以更有效.

  • 我不明白使用storageEvent如何帮助解决并发问题.任何人都可以提供一个代码示例,该代码在localStorage中增加一个值,并且由于使用storageEvents而没有并发问题? (4认同)
  • @ user1608790这样的例子不存在. (2认同)
  • ……不可能存在?Javascript 一次只为特定页面运行一个功能。如果我没有记错你不能提取然后存储提取值的增量版本,并确保在两个步骤之间(更多,因为增量是它自己的一个步骤)另一个选项卡没有改变它的值,并且您可能会丢失至少 1 个增量。 (2认同)