Мьютекс в JavaScript - это похоже на правильную реализацию? - PullRequest
0 голосов
/ 28 июня 2018

Не совсем серьезный вопрос, скорее, душевная мысль: ключевое слово JavaScript await должно допускать нечто, похожее на мьютекс в вашем обычном "параллельном языке".

function Mutex() {
    var self = this; // still unsure about how "this" is captured
    var mtx = new Promise(t => t()); // fulfilled promise ≡ unlocked mutex
    this.lock = async function() {
        await mtx;
        mtx = new Promise(t => {
            self.unlock = () => t();
        });
    }
}
// Lock
await mutex.lock();
// Unlock
mutex.unlock();

Это правильная реализация (кроме правильной обработки ошибок)? И ... могу ли я иметь C ++ - охранники блокировки в стиле RAII?

Ответы [ 2 ]

0 голосов
/ 28 июня 2018

Это правильная реализация?

Нет. Если две задачи (я не могу сказать «потоки») пытаются выполнить 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");
0 голосов
/ 28 июня 2018

Ваша реализация позволяет столько потребителей получить блокировку, сколько просят ее; каждый вызов lock ожидает одно обещание:

function Mutex() {
    var self = this; // still unsure about how "this" is captured
    var mtx = new Promise(t => t()); // fulfilled promise ≡ unlocked mutex
    this.lock = async function() {
        await mtx;
        mtx = new Promise(t => {
            self.unlock = () => t();
        });
    }
}

const mutex = new Mutex();

(async () => {
  await Promise.resolve();
  await mutex.lock();
  console.log("A got the lock");
})();
(async () => {
  await Promise.resolve();
  await mutex.lock();
  console.log("B got the lock");
})();

Вам нужно реализовать очередь обещаний, создавая новую для каждого запроса блокировки.

Примечания:

  • new Promise(t => t()) можно записать проще и идиоматичнее Promise.resolve(): -)
  • Нет необходимости в self, если вы используете такие функции стрелок; функции стрелок закрывают this, где они созданы (точно так же, как закрывают переменную)
  • Возможно, имеет смысл для unlock быть значением разрешения обещания блокировки, поэтому только код, получивший блокировку, может снять его

Примерно так:

function Mutex() {
    let current = Promise.resolve();
    this.lock = () => {
        let _resolve;
        const p = new Promise(resolve => {
            _resolve = () => resolve();
        });
        // Caller gets a promise that resolves when the current outstanding
        // lock resolves
        const rv = current.then(() => _resolve);
        // Don't allow the next request until the new promise is done
        current = p;
        // Return the new promise
        return rv;
    };
}

Live Пример:

"use strict";
function Mutex() {
    let current = Promise.resolve();
    this.lock = () => {
        let _resolve;
        const p = new Promise(resolve => {
            _resolve = () => resolve();
        });
        // Caller gets a promise that resolves when the current outstanding
        // lock resolves
        const rv = current.then(() => _resolve);
        // Don't allow the next request until the new promise is done
        current = p;
        // Return the new promise
        return rv;
    };
}

const rand = max => Math.floor(Math.random() * max);

const delay = (ms, value) => new Promise(resolve => setTimeout(resolve, ms, value));

const mutex = new Mutex();

function go(name) {
    (async () => {
        console.log(name + " random initial delay");
        await delay(rand(50));
        console.log(name + " requesting lock");
        const unlock = await mutex.lock();
        console.log(name + " got lock");
        await delay(rand(1000));
        console.log(name + " releasing lock");
        unlock();
    })();
}
go("A");
go("B");
go("C");
go("D");
.as-console-wrapper {
  max-height: 100% !important;
}
...