Javascript,异步,锁定?

时间:2017-12-13 15:53:49

标签: javascript node.js asynchronous promise

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

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给我们的异步优势。如果在提示之前处理的魔法很重,则在提示之前需要一些时间。

主意?

2 个答案:

答案 0 :(得分:3)

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

  1. 您希望以尽可能多的并行性运行一系列异步操作(序列化所需的最少量)。
  2. 一个或多个操作可能需要提示用户输入信息。
  3. 所有用户提示必须按照精确的顺序排列,以了解代码的排序方式。因此,第一个被调用的函数和提示必须先提示。
  4. 如果同一功能提示不止一次,则其所有提示必须先于其他任何提示。
  5. 基本上,所有提示都必须序列化,但提示之前或之后的任何其他异步内容都可以并行运行。
  6. 最好的代码不会“旋转”或“轮询”一个标志,但是当等待的东西准备好运行时(通过承诺背后的一个重要原则),通过获得通知来保留其他操作的CPU周期。
  7. 这是一个以您发布的方式为模型的方案,但它使用一系列承诺(不旋转或轮询标记)来强制prompt()调用的序列化(.start()和{之间的任何内容{1}}调用,同时允许所有其他操作并行运行。这对CPU使用率应该更高效。

    .end()

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

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

    一些解释:

    1. 你在代码中添加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 的地方就是你将这个函数“排成一行”以进行序列化,所以还没有让自己排成一行的是它的关键部分被迫来在这个功能的关键部分之后。这“保留”了一个位置,但实际上还没有开始一个关键部分。如果您在临界区之前运行其他不确定的异步代码,这一点很重要。您需要在异步代码之前预先保留您的位置,但不要在临界区之前等待实际排队。

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

    3. 放置await s.start();的位置是关键部分的结束(现在允许其他关键部分也在运行时)。

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

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

答案 1 :(得分:-2)

经过一段时间的测试,终于得到了答案!受信号量概念的启发,我创造了以下内容:

const Semaphore = (n) => ({
  n,
  async down(){
    while(this.n <= 0) await this.wait();
    this.n--;
  },
  up(){
    this.n++;
  },
  async wait(){
    if(this.n <= 0) return await new Promise((res, req) => {
      setImmediate(async () => res(await this.wait()))
    });
    return;
  },
});

const prompt = (n) => {
  console.log('input please: ' + n);
  return new Promise((res, rej) => {
    setTimeout(() => res(), 3000)
  });
};

const semaphore = Semaphore(1);

async function magic1(){
  console.log('do some busy work 1');
  await semaphore.down();
  await prompt(1);
  await prompt(2);
  semaphore.up();
}

async function magic2(){
  console.log('do some busy work 2');
  await semaphore.down();
  await prompt(3);
  await prompt(4);
  semaphore.up();
}

magic1();
magic1();
magic2();
magic2();

结果:

do some busy work 1
do some busy work 1
do some busy work 2
do some busy work 2
input please: 1
input please: 2
input please: 1
input please: 2
input please: 3
input please: 4
input please: 3
input please: 4

这个信号量基本上很忙等待n == 0。虽然我称之为繁忙等待,但它实际上并不是因为setImmediate将允许事件循环中的其他函数执行!到目前为止,这个实现是迄今为止最好,最优雅的工作解决方案。

有时会说[1,2] [3,4] [1,2] [3,4]。但那没关系!这是异步的!