gpt4 book ai didi

JavaScript、异步、锁?

转载 作者:行者123 更新时间:2023-11-28 17:41:40 27 4
gpt4 key购买 nike

我知道异步不是并行的,但我现在遇到了一个非常有趣的情况。

async function magic(){
/* some processing here */
await async () => await prompt_for_user(); // 1st prompt
await async () => await prompt_for_user(); // 2nd prompt
}

magic(); // first
magic(); // second
magic(); // third

从上面的程序中,我们可以轻松预测所有提示会同时弹出。我尝试使用以下实现的队列来解决它:

 const Queue = () => {
let promise;
return async (f) => {
while(promise) await promise;
promise = f();
const res = await promises;
promise = undefined;
return res;
};
};


const queue = Queue();
async function magic(){
/* some processing here */
await queue(async () => await prompt_for_user()); // 1st prompt
await queue(async () => await prompt_for_user()); // 2nd prompt
}

magic(); // first
magic(); // second
magic(); // third

这会阻止一次全部弹出提示。但还有第二个问题:

所以当第一个 magic() 被调用时。向用户显示提示 first.1。程序继续并调用第二个 magic()。另一个提示 second.1 正在等待第一个提示完成后再出现。然后程序继续,调用第三个 magic(),并且 third.1 再次等待 first.1 完成。当 first.1 完成时,意味着用户已经输入了值,second.1 将首先弹出,但我希望 first.2 弹出先起来。

我知道一个明显的解决方案是一一等待魔法。但是这样就会失去js给我们带来的异步优势。如果提示前处理的魔法较多,则提示前需要一些时间。

想法?

最佳答案

由于在您发布信号量答案之前我无法理解您的总体目标,因此我将尝试回答的问题定义如下。

  1. 您希望以尽可能多的并行度运行一系列异步操作(串行化所需的最少数量)。
  2. 一项或多项操作可能需要提示用户输入信息。
  3. 所有用户提示都必须按照代码的精确顺序排列。因此,第一个被调用和提示的函数必须首先提示。
  4. 如果同一函数多次提示,则其所有提示必须出现在其他提示之前。
  5. 所以基本上,所有提示都必须序列化,但提示之前或之后出现的任何其他异步内容都可以并行运行。
  6. 最好的代码不会“旋转”或“轮询”标志,而是会在等待的内容准备好运行时收到通知,从而为其他操作保留 CPU 周期(promise 背后的一大原则)。

这是一个方案,以您发布的方案为模型,但它使用一系列 promise (不旋转或轮询标志)来强制序列化 prompt() 调用( >.start().end() 调用,同时允许所有其他操作并行运行。这对于 CPU 使用率来说应该会更加高效。

let Semaphore = (function() {
// private data shared among all instances
let sharedPromise = Promise.resolve();

return class Sempaphore {
constructor() {
let priorP = sharedPromise;
let resolver;

// create our promise (to be resolved later)
let newP = new Promise(resolve => {
resolver = resolve;
});

// chain our position onto the sharedPromise to force serialization
// of semaphores based on when the constructor is called
sharedPromise = sharedPromise.then(() => {
return newP;
});

// allow caller to wait on prior promise for its turn in the chain
this.start = function() {
return priorP;
}

// finish our promise to enable next caller in the chain to get notified
this.end = function() {
resolver();
}
}
}
})();

// use random times to test our async better
function prompt(tag, n) {
log(tag, 'input please: ', n);
return new Promise((resolve) => {
setTimeout(resolve, Math.floor(Math.random() * 1000) + 500);
});
};

function log(...args) {
if (!log.start) {
log.start = Date.now();
}
let diff = ((Date.now() - log.start) / 1000).toFixed(3);
console.log(diff + ": ", ...args);
}

function randomDelay(low = 500, high = 1000) {
return new Promise((resolve) => {
setTimeout(resolve, Math.floor(Math.random() * (high - low)) + low);
});
}

async function magic1(tag){
// declare semaphore before any async code to reserve your order for semaphored code below
let s = new Semaphore();

// whatever sync or async code you want here
log(tag, 'do some busy async work 1a');
await randomDelay(800, 1200);
log(tag, 'do some busy work 1b');

// start of our serialized critical section
await s.start();
await prompt(tag, 1);
await prompt(tag, 2);
s.end();
// end of our serialized critical section

// whatever sync or async code you want here
log(tag, 'do more busy work 1c');
await randomDelay();
}

async function magic2(tag){
let s = new Semaphore();
log(tag, 'do some busy async work 2a');
// this delay purposely causes magic2 async delay to be shorter
// than magic1 for testing purposes
await randomDelay(100, 750);
log(tag, 'do some busy work 2b');
await s.start();
await prompt(tag, 3);
await prompt(tag, 4);
s.end();
log(tag, 'do more busy work 2c');
await randomDelay();
}

Promise.all([
magic1("magic1a"),
magic1("magic1b"),
magic2("magic2a"),
magic2("magic2b")
]).then(() => {
log("all done");
}).catch(err => {
log("err: ", err);
});

这是一些示例输出(由于出于测试目的而进行的随机异步延迟,输出会略有不同)。但是,输入调用将始终按照完全相同的顺序:

0.000:  magic1a do some busy async work 1a
0.003: magic1b do some busy async work 1a
0.004: magic2a do some busy async work 2a
0.004: magic2b do some busy async work 2a
0.600: magic2b do some busy work 2b
0.721: magic2a do some busy work 2b
0.829: magic1b do some busy work 1b
1.060: magic1a do some busy work 1b
1.061: magic1a input please: 1
2.179: magic1a input please: 2
2.860: magic1a do more busy work 1c
2.862: magic1b input please: 1
3.738: magic1b input please: 2
4.500: magic1b do more busy work 1c
4.502: magic2a input please: 3
5.845: magic2a input please: 4
6.497: magic2a do more busy work 2c
6.498: magic2b input please: 3
7.516: magic2b input please: 4
8.136: magic2b do more busy work 2c
9.097: all done

一些解释:

  1. 在代码中放置 let s = new Sempaphore(); 的位置就是该函数“将自身放入队列”以进行序列化的位置,以便尚未将其自身放入队列中的东西在行中,它的关键部分被迫位于该函数的关键部分之后。这“保留”了一个队列位置,但实际上尚未开始关键部分。如果您有其他不确定的异步代码在关键部分之前运行,这一点很重要。您需要在异步代码之前保留排队位置,但直到关键部分之前才真正等待排队位置。

  2. 在函数中放置 await s.start(); 的位置是您希望它实际等待关键部分的位置的位置。

    <
  3. 放置 s.end() 的位置是关键部分的末尾(允许其他关键部分现在也可以在轮到它们时运行)。

  4. 此演示显示了在提示的关键部分之前和之后运行的异步代码。该代码可以与其他代码并行运行。

  5. 即使在同一关键部分(根据设计),非输入相关的异步操作也可以在输入提示之间交错。仅输入提示被强制序列化。

关于JavaScript、异步、锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47797221/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com