Это правильная реализация?
Нет. Если две задачи (я не могу сказать «потоки») пытаются выполнить mutex.lock()
, пока они заблокированы, они обе получат блокировку одновременно. Я сомневаюсь, что это то, что вы хотите.
Мьютекс в JS - это на самом деле просто логический флаг - вы проверяете его, устанавливаете его, когда получаете блокировку, вы очищаете его, когда снимаете блокировку. Нет особой обработки условий гонки между проверкой и получением, так как вы можете делать это синхронно в однопоточном JS, без вмешательства других потоков.
Однако вам кажется, что вы ищете очередь , то есть то, что вы можете запланировать самостоятельно, чтобы получить блокировку, и будут уведомлены (через обещание), когда будет снята предыдущая блокировка.
Я бы сделал это с
class Mutex {
constructor() {
this._lock = null;
}
isLocked() {
return this._lock != null;
}
_acquire() {
var release;
const lock = this._lock = new Promise(resolve => {
release = resolve;
});
return () => {
if (this._lock == lock) this._lock = null;
release();
};
}
acquireSync() {
if (this.isLocked()) throw new Error("still locked!");
return this._acquire();
}
acquireQueued() {
const q = Promise.resolve(this._lock).then(() => release);
const release = this._acquire(); // reserves the lock already, but it doesn't count
return q; // as acquired until the caller gets access to `release` through `q`
}
}
Демо-версия:
class Mutex {
constructor() {
this._lock = Promise.resolve();
}
_acquire() {
var release;
const lock = this._lock = new Promise(resolve => {
release = resolve;
});
return release;
}
acquireQueued() {
const q = this._lock.then(() => release);
const release = this._acquire();
return q;
}
}
const delay = t => new Promise(resolve => setTimeout(resolve, t));
const mutex = new Mutex();
async function go(name) {
await delay(Math.random() * 500);
console.log(name + " requests lock");
const release = await mutex.acquireQueued();
console.log(name + " acquires lock");
await delay(Math.random() * 1000);
release()
console.log(name + " releases lock");
}
go("A");
go("B");
go("C");
go("D");