【问题标题】:JavaScript Double Null Check and LockingJavaScript 双重空值检查和锁定
【发布时间】:2015-01-28 21:42:15
【问题描述】:

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

伪代码:

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

第一次调用此函数时,我会延迟加载小型数据库表中的所有记录,然后在后续调用中返回内存中的记录。

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

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

该解决方案的问题是:

  1. 它很脆弱,如果第一个异步调用抛出并且没有解除锁定怎么办。
  2. 在放弃之前,我们循环回到计时器多少次?
  3. 定时器应该设置多长时间?在某些环境中,1 秒可能太长且效率低下。

是否有在 JavaScript 中解决此问题的最佳实践?

【问题讨论】:

  • 我会使用承诺。在第一次调用这个函数时,创建一个 Promise 并返回它。在随后的调用中,返回相同的承诺。有数据时解决。
  • @KevinB 如果您重新使用相同的 Promise 实例,如何将单独的回调保持直接?我猜 Promise 库只会处理多个 .then() 调用并做正确的事情? (我没有写过很多基于 Promise 的代码,至少除了超级简单的东西之外没有太多。)
  • 这取决于实现(除了超简单的承诺之外,我还没有做太多事情)但是我很确定 .then() 如果从传入的返回任何内容,则返回一个新的承诺回调,意思是在这个 promise 上多次使用 .then 应该不会影响它的结果,除非你用 thepromise = thepromise.then(... 覆盖它

标签: javascript multithreading asynchronous locking


【解决方案1】:

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

在后续调用中,如果数据存在,则返回一个已经实现的 Promise。如果没有,则在数组中添加另一个 Promise 并返回。

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

【讨论】:

    【解决方案2】:

    我真的很喜欢另一个答案中的承诺解决方案——非常聪明,非常有趣。 Promise 不是主要的方法,因此您可能需要对团队进行教育。不过我打算换个方向。

    您所追求的是一个 memoize 函数 - 一个存储昂贵结果的内存键/值缓存。 JavaScript the Good Parts 在末尾有一个memoize 示例。 Lodash 有一个memoize 函数。这些假设是同步处理,因此不考虑您的情况 - 也就是说,它们会多次访问数据库,直到其中一个“线程”回复。

    异步库还有一个memoize 函数,可以完全满足您的需求。在它的内部,它保留了一个回调队列数组,一旦它得到答案,它就会缓存它并调用所有的回调。

    如果您喜欢发明,请务必使用 Promise。如果您只是想要即插即用的答案,请使用 async#memoize。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多