Ответ 1
Да, это поточно-безопасный. Однако ваш код не является атомарным, и это ваша проблема. Я доберусь до уровня безопасности localStorage
, но сначала, как исправить вашу проблему.
Обе вкладки могут проходить проверку if
вместе и записывать в элемент, переписывающий друг друга. Правильный способ решения этой проблемы - использовать StorageEvent
s.
Они позволяют вам уведомлять другие окна, когда ключ изменился в localStorage, эффективно решая проблему для вас во встроенном сообщении, передающем безопасный путь. Вот о них приятно читать. Приведем пример:
// tab 1
localStorage.setItem("Foo","Bar");
// tab 2
window.addEventListener("storage",function(e){
alert("StorageChanged!"); // this will run when the localStorage is changed
});
Теперь, что я обещал о безопасности потоков:)
Как мне нравится - давайте наблюдать это с двух сторон - из спецификации и использования реализации.
Спецификация
Покажите это потокобезопасным по спецификации.
Если мы проверим спецификацию Web Storage, мы увидим, что она особенно примечания:
Из-за использования мьютекса хранения несколько контекстов просмотра смогут одновременно обращаться к локальным областям хранения таким образом, что скрипты не могут обнаружить какое-либо одновременное выполнение script.
Таким образом, атрибут length объекта Storage и значение различных свойств этого объекта не могут измениться, пока выполняется script, отличным от того, что предсказуемо самой script.
Он еще более подробно описывает:
Всякий раз, когда свойства объекта
localStorage
атрибут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;
}
Далее, это происходит в 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);
}
Обратите внимание, что 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;
}
Они также дошли до замечательной заметки:
// FIXME: In the future, we should allow use of StorageAreas while it 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 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.
Объяснение этого работает, но оно может быть более эффективным.