问题:我想用localStorage在多个tab下共享数据,但是看了下localStorage里面没有像C++的Mutex一样的锁,那我怎么保证原子的读和写?
答:C++的mutex是使用操作系统的原子性操作实现的,在浏览器环境下没有什么参考的价值。不过,
在这篇文章里面,大牛Lamport介绍了利用两块共享区域实现快速锁的算法,如下所示:
假设有两块共享的区域x和y,以及进程的id,以下图的方式进入关键区域
进入关键区域需要的操作至少需要: 写x -> 读y -> 写y -> 读x -> 关键操作 -> 写y。
其中delay必须足够长到使得已经在关键区域内的进程完成关键区域内的操作并退出。
Lamport的文章也证明了这个算法的正确性,可以通过这个链接了解详细的信息。
有了这个算法之后,实现一个localStorage上的锁就很简单啦,下面是我的一个简单实现:
const THREAD_ID = `${Math.floor(Math.random() * 1000)} - ${Date.now()}`;
const setItem = localStorage.setItem.bind(localStorage);
const getItem = localStorage.getItem.bind(localStorage);
const removeItem = localStorage.removeItem.bind(localStorage);
const nextTick = fn => setTimeout(fn, 0);
class Mutex {
constructor (key) {
this.keyX = `mutex_key_${key}_X`;
this.keyY = `mutex_key_${key}_Y`;
}
lock() {
return new Promise((resolve, reject) => {
const fn = () => {
setItem(this.keyX, THREAD_ID);
if (!getItem(this.keyY) == null) {
nextTick(fn); //restart
}
setItem(this.keyY, THREAD_ID);
if (getItem(this.keyX) !== THREAD_ID) {
//delay
setTimeout(() => {
if (getItem(this.keyY) !== THREAD_ID) {
nextTick(fn) //restart
return;
}
//critical section
resolve();
removeItem(this.keyY);
}, 10);
} else {
resolve();
removeItem(this.keyY);
}
};
fn();
});
}
}
const KEY = 'key';
let mutex = new Mutex(KEY);
mutex.lock().then(() => {
let value = parseInt(localStorage.getItem(KEY) || 0, 10);
value += 1;
localStorage.setItem(KEY, value);
});