我是否需要关注异步Javascript的竞争条件?

时间:2011-08-30 04:11:53

标签: javascript

假设我加载了一些我以后知道的Flash电影将调用window.flashReady并设置window.flashReadyTriggered = true

现在我有一段代码,我想在Flash准备就绪时执行。如果已经调用window.flashReady,我希望它立即执行它,如果尚未调用它,我想将它作为window.flashReady中的回调。天真的方法是:

if(window.flashReadyTriggered) {
  block();
} else {
  window.flashReady = block;
}

所以基于此问题的关注点是if条件中的表达式被评估为false,但是在block()被执行之前,window.flashReady是由外部Flash触发。因此,永远不会调用block

是否有更好的设计模式来实现我想要达到的更高级别目标(例如,手动调用flashReady回调)?如果没有,我是安全的,还是我应该做的其他事情?

5 个答案:

答案 0 :(得分:21)

所有Javascript事件处理程序脚本都是从一个主事件队列处理的。这意味着事件处理程序一次运行一个,一个运行直到完成,然后下一个准备开始运行。因此,Javascript中没有任何典型的竞争条件可以在多线程语言中看到,其中语言的多个线程可以一次运行(或时间切片)并且与变量的访问冲突。 javascript中的任何单独执行线程将在下一个启动之前运行完成。这就是Javascript的工作原理。从事件队列中拉出事件,代码开始运行以处理该事件。该代码自行运行,直到它将控制权返回给系统,然后系统将从事件队列中提取下一个事件并运行该代码,直到它将控制权返回给系统。

因此,在Javascript中不会发生由同时执行的两个执行线程引起的典型竞争条件。

这包括所有形式的Javascript事件,包括:用户事件(鼠标,键等),计时器事件,网络事件(ajax回调)等......

你可以在Javascript中实际执行多线程的唯一地方是使用HTML5 Web Workers,但它们与常规javascript非常隔离(它们只能通过消息传递与常规javascript通信)并且不能在以下操作DOM所有,必须有自己的脚本和命名空间等...


虽然我在技术上不会将其称为竞争条件,但在Javascript中存在一些情况,因为它可能会运行两个或多个异步操作,并且当每个操作相对于其他操作完成时可能无法预测。这会产生时间的不确定性(如果操作的相对时间对您的代码很重要)会产生您必须手动编码的内容。您可能需要对操作进行排序,以便在开始下一个操作之前运行并确实等待它完成。或者,您可以启动所有三个操作,然后使用一些代码收集所有三个结果,当它们都准备就绪时,您的代码就会继续运行。

在现代Javascript中,promises通常用于管理这些类型的异步操作。

所以,如果你有三个异步操作,每个都返回一个promise(比如从数据库读取,从另一个服务器获取请求等等),你可以像这样手动排序:

a().then(b).then(c).then(result => {
    // result here
}).catch(err => {
    // error here
});

或者,如果你想让它们一起运行(同时在飞行中)并且只知道它们何时完成,你可以这样做:

Promise.all([a(), b(), c()])..then(results => {
    // results here
}).catch(err => {
    // error here
});

虽然我不打算称这些竞争条件,但它们与设计代码的一般系列一致,以控制不确定的排序。


在浏览器的某些情况下可能会出现一种特殊情况。这不是真正的竞争条件,但如果你使用临时状态的许多全局变量,可能需要注意一些事情。当您自己的代码导致另一个事件发生时,浏览器有时会同步调用该事件处理程序,而不是等到当前执行的线程完成。一个例子是:

  1. 点击
  2. click事件处理程序将焦点更改为另一个字段
  3. 其他字段具有onfocus的事件处理程序
  4. 浏览器立即调用onfocus事件处理程序
  5. onfocus事件处理程序运行
  6. 单击事件处理程序的其余部分运行(在.focus()调用之后)
  7. 这在技术上不是竞争条件,因为当onfocus事件处理程序将执行时(在.focus()调用期间)它是100%已知的。但是,它可以创建一个事件处理程序运行而另一个处于执行中间的情况。

答案 1 :(得分:14)

JavaScript是单线程的。没有竞争条件。

当你的当前“指令指针”没有更多代码要执行时,“thread”“传递接力棒”,排队的window.setTimeout或事件处理程序可以执行其代码。

您将更好地了解Javascript的单线程方法,了解node.js的设计理念。

进一步阅读: Why doesn't JavaScript support multithreading?

答案 2 :(得分:7)

重要的是要注意,如果您例如,您可能仍会遇到竞争条件。使用多个异步XMLHttpRequest。返回响应的顺序未定义(即响应可能不会按照发送的顺序返回)。这里的输出取决于其他不可控事件的顺序或时间(服务器延迟等)。简而言之,这是竞争条件

因此,即使使用单个事件队列(如在JavaScript中)也不会阻止事件以无法控制的顺序进行,您的代码应该处理这个问题。

答案 3 :(得分:1)

当然你需要。它一直在发生:

<button onClick=function() {
  const el = document.getElementById("view");
  fetch('/some/api').then((data) => {
    el.innerHTML = JSON.stringify(data);
  })
}>Button 1</button>

<button onClick=function() {
  const el = document.getElementById("view");
  fetch('/some/other/api').then((data) => {
    el.innerHTML = JSON.stringify(data);
  })

}>Button 2</button>

有些人并不认为这是一种竞争条件。

但确实如此。

竞争条件大致定义为“电子,软件或其他系统的行为,其输出取决于其他无法控制的事件的顺序或时间”。

如果用户在短时间内单击这两个按钮,则无法保证输出取决于单击顺序。这取决于哪个api请求会更快解决。此外,您引用的DOM元素可以通过其他一些事件(如更改路径)删除。

您可以通过在加载正在进行的操作时禁用按钮或显示一些微调器来缓解此竞争条件,但这是作弊。您应该在代码级别使用一些互斥/计数器/信号量来控制异步流。

要使其适应您的问题,它取决于“block()”是什么。如果它是同步功能,您无需担心。但如果它是异步的,你必须担心:

  function block() {
    window.blockInProgress = true;
    // some asynchronous code
    return new Promise(/* window.blockInProgress = false */);
  }

  if(!window.blockInProgress) {
    block();
  } else {
    window.flashReady = block;
  }

此代码有意义,您希望防止多次调用块。但是如果你不在乎,或者“阻止”是同步的,你不必担心。如果你担心全局变量值在检查时会发生变化,你不应该担心,除非你调用一些异步函数,否则保证不会改变。

一个更实际的例子。考虑我们想要缓存AJAX请求。

 fetchCached(params) {
   if(!dataInCache()) {
     return fetch(params).then(data => putToCache(data));
   } else {
     return getFromCache();
   }
 }

如果我们多次调用此代码会发生这种情况吗?我们不知道哪些数据会先返回,所以我们不知道哪些数据会被缓存。前2次会返回新数据,但第3次我们不知道要返回的响应形状。

答案 4 :(得分:0)

是的,当然Java语言中存在竞争条件。它基于事件循环模型,因此显示了用于异步计算的竞争条件。以下程序将根据1016首先完成来记录incHeadsqrHead

const rand = () => Math.round(Math.random() * 100);

const incHead = xs => new Promise((res, rej) =>
  setTimeout(ys => {
    ys[0] = ys[0] + 1;
    res(ys);
  }, rand(), xs));

const sqrHead = xs => new Promise((res, rej) =>
  setTimeout(ys => {
    ys[0] = ys[0] * ys[0];
    res(ys);
  }, rand(), xs))

const state = [3];

const foo = incHead(state);

const bar = sqrHead(state);

Promise.all([foo, bar])
  .then(_ => console.log(state));