JavaScript双空检查和锁定

时间:2015-01-28 21:42:16

标签: javascript multithreading asynchronous locking

在具有线程和锁的语言中,通过检查变量的值很容易实现延迟加载,如果它为null,则锁定下一段代码,再次检查值,然后加载资源并分配。这可以防止它被多次加载并导致第一个线程之后的线程等待第一个线程完成所需的操作。

Psuedo代码:

  if(myvar == null) {
    lock(obj) {
      if(myvar == null) {
        myvar = getData();
      }
    }
  }
  return myvar;

JavaScript在单个线程中运行,但是,由于异步执行而一个调用正在等待阻塞资源,因此它仍然存在此类问题。在这个Node.js示例中:

var allRecords;
module.exports = getAllRecords(callback) {
  if(allRecords) {
    return callback(null,allRecords);
  }

  db.getRecords({}, function(err, records) {
    if (err) {
      return callback(err);
    }

    // Use existing object if it has been
    // set by another async request to this
    // function
    allRecords = allRecords || partners;

    return callback(null, allRecords);
  });
}

我在第一次调用此函数时从一个小型DB表中加载所有记录,然后在后续调用中返回内存中的记录。

问题:如果同时向此函数发出多个异步请求,则表将不必要地从DB中多次加载该表。

为了解决这个问题,我可以通过创建var lock;变量并在加载表时将其设置为true来模拟锁定机制。然后我会将其他异步调用放入setTimeout()循环中,每隔(比如说)1秒检查一次该变量,直到数据可用,然后允许它们返回。

该解决方案的问题是:

  1. 它很脆弱,如果第一个异步调用抛出并且没有取消锁定该怎么办。
  2. 在放弃之前,我们会多少次循环回计时器?
  3. 定时器应设置多长时间?在某些环境中,1秒可能太长而且效率低下。
  4. 在JavaScript中有解决此问题的最佳做法吗?

2 个答案:

答案 0 :(得分:2)

在第一次调用服务时,初始化一个数组。开始获取操作。创建一个Promise,将其存储在数组中。

在后续调用中,如果数据存在,则返回已完成的Promise。如果没有,请将另一个Promise添加到数组并返回它。

当数据到达时,解析列表中所有等待的Promise对象。 (一旦数据存在,您就可以丢弃列表。)

答案 1 :(得分:1)

我非常喜欢其他答案中的承诺解决方案 - 非常聪明,非常有趣。承诺不是一种重要的方法,因此您可能需要教育团队。不过我会朝另一个方向走。

你所追求的是一个memoize函数 - 一个昂贵结果的内存中键/值缓存。 JavaScript Good the Parts最后有一个memoize样本。 Lodash具有memoize功能。这些假设是同步处理所以不要考虑你的场景 - 也就是说他们很多次都会在数据库中点击,直到其中一个“线程”回复为止。

异步库还具有memoize功能,可以完全按照您的要求执行操作。在它的内部,它保留了一个回调队列数组,一旦得到答案,它就会缓存它并调用所有的回调。

如果你是通过各种方式发明,使用承诺。如果您只是喜欢即插即用的答案,请使用async#memoize。