【问题标题】:Mutex in JavaScript - does this look like a correct implementation?JavaScript 中的互斥锁 - 这看起来像一个正确的实现吗?
【发布时间】:2018-06-28 15:31:34
【问题描述】:

不是一个完全严肃的问题,更像是一个淋浴的想法: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 风格的锁守卫吗?

【问题讨论】:

  • new Promise(t => t()) => Promise.resolve()
  • 如果你使用箭头函数,也不需要var self = this;(因为你说你不确定)。

标签: javascript mutex


【解决方案1】:

您的实现允许尽可能多的消费者获得锁;每次调用 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;
    };
}

现场示例:

"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;
}

【讨论】:

  • ... 为什么我认为我不需要那个。感谢您提供干净简洁的代码。不过有一个问题:memo 参数有什么作用?
  • @Caesar - 哈哈,这是我测试的剩余部分。 (我将name 传递给mutex.lock 进行日志记录。然后我删除了所有日志记录但忘记删除参数。)现在删除。 :-)
  • 为什么_resolve = () => resolve(),顺便说一句?不是和_resolve = resolve一样吗?还是我见过太多的 η 收缩?
  • @Caesar - 不完全相同。 _resolve = resolve 意味着传递给_resolve 的任何参数都可以控制promise(设置分辨率值,甚至通过使用被拒绝的promise [或未决并最终被拒绝的] 调用它来导致拒绝)。 _resolve = () => resolve() 明确避免传递它可能接收到的任何参数,并确保在没有参数的情况下调用 resolve(因此 promise 使用 undefined 解析)。
【解决方案2】:

这是一个正确的实现吗?

没有。如果两个任务(我不能说“线程”)在当前锁定时尝试执行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");

【讨论】:

  • 使示例可运行会很有帮助。
  • @T.J.Crowder 仍在努力。承认acquireSync 让这变得非常困难......
  • 当我离开的时候我意识到我做错了什么;现在排序。
  • @T.J.Crowder 我刚刚意识到我们不需要current = current.then(() => p); 但可以做current = p; - pcurrent 之前无法解析,因为解析器功能不能被访问和调用
  • 我可以发誓我在最后尝试过(因为我有相同的推理),并且确实可以发誓我很早就尝试过,但我一定是搞砸了。我现在已经更新了。
【解决方案3】:

我建议使用像 async-mutex 这样的库:

const mutex = new Mutex();
// ...
const release = await mutex.acquire();
try {
    // ...
} finally {
    release();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-11
    • 1970-01-01
    • 1970-01-01
    • 2014-02-28
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多