2011-08-30 74 views
11

假设我加载了一些我知道在未来某个时候会播放的Flash影片,将会调用window.flashReady并设置window.flashReadyTriggered = true我需要关心异步Javascript的竞争条件吗?

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

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

所以我在此基础上值得关注的是,在if条件表达式被false,但在那之前可以执行block()window.flashReady由外部闪光灯触发。因此,block永远不会被调用。

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

+0

为什么不只是让flash调用javascript函数呢? – zellio

回答

13

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

如果在当前的“指令指针”处没有更多的代码要执行,则“线程”“通过接力棒”,并且排队的队列或事件处理程序可以执行其代码。

通过阅读node.js的设计思路,您将更好地理解Javascript的单线程方法。

延伸阅读: Why doesn't JavaScript support multithreading?

+0

感谢您直接提供的简洁答案。我想从一开始我就应该用这种方式说出我的问题。 –

+0

这个答案不是完整的故事。请阅读我的答案如下:http://stackoverflow.com/a/12799287/1040124 – Jens

+0

@Jens,传入的事件不会抢占当前的代码,但你是正确的,有回调将接下来会触发的种族。 – kay

17

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

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

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

你其实可以做的唯一的地方Javascript中的多线程与HTML5 Web Workers是一样的,但它们与常规javascript非常分离(它们只能通过消息传递与常规JavaScript进行通信),并且根本无法操作DOM,并且必须拥有自己的脚本和命名空间等。


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

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

所以,如果你有三个异步操作,每个返回一个承诺(如从数据库中读取,获取来自其他服务器的请求,等...),你可以手动序列,然后将这样的:

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

虽然我不会把这些竞态条件,他们在设计你的代码来控制inde的同一个大家族里终止排序。


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

  1. 点击
  2. 单击事件处理程序的变化集中到其他领域
  3. 其他领域具有的onfocus事件处理程序
  4. 浏览器调用立即onfocus事件处理程序
  5. onfocus事件处理程序运行
  6. 其余的click事件处理程序运行(在.focus()调用之后)

这在技术上不是竞争条件,因为在onfocus事件处理程序将执行时(在调用.focus()期间),它是100%已知的。但是,它可以创建一个事件处理程序运行而另一个处于执行中的情况。

+0

“任何单独的执行线程”<---那是什么?它是单个事件循环迭代吗?或者它是单个事件处理程序执行? – Vanuan

+1

你需要定义什么样的竞争条件。竞态条件与线程无关。 – Vanuan

+0

@Vanuan - 因为在任何给定时间只有一块Javascript运行,所以没有一种典型的竞争条件可以通过线程看到两个线程试图访问相同的变量或一个线程访问它的时间与另一个是完全不可预测的。由于一次只能运行一段JavaScript,所以这种类型的竞争条件根本不可能发生。如果你想问一个不同类型的竞争条件,那么请解释你在问什么,因为在这个问题上没有其他人显然是在问这个问题。 – jfriend00

7

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

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

+1

这不是竞争条件,它只是异步代码的工作原理。如果你给鲍勃一个盒子,并且说“当你打开这个盒子时,把它交给我”,然后立即转向苏并告诉她同样的事情,你是从第一个盒子回来的?这取决于他们打开盒子需要多长时间,并且不在您的控制范围之内。同样,AJAX请求的工作方式也是一样的。你不能假定他们会按照任何特定的顺序完成,因为你没有订购它们,你只是说“当请求完成时,运行这个回调函数”。 –

+3

竞争条件不一定会发生在线程中,它只是一种说法,表示您有两个或更多的异步函数竞争同一资源,并且您不知道谁会赢,以及执行的最终状态是什么。这就是为什么@Jens描述的是正确的 –

-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元素可以通过其他事件(如更改路由)来移除。

您可以通过禁用按钮或在加载操作时显示某个微调器来减轻这种竞争条件,但这是作弊。您应该在代码级别拥有一些互斥/计数器/信号量来控制异步流程。

为了适应您的问题,它取决于“块()”是什么。如果它是同步功能,则不必担心。但是,如果它是异步的,你必须担心:

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

如果我们多次调用这段代码,会发生什么情况?我们不知道哪些数据会先返回,因此我们不知道哪些数据将被缓存。前两次它会返回新的数据,但第三次我们不知道要返回的响应形状。

+0

这不是一个竞争条件,它是对异步代码工作方式的误解。使用一个更加标准的例子,想象一下,如果你问两个人去办公桌,拿起一支笔,然后回来放在你的桌子上,他会先把笔放在你的办公桌上?你不知道,因为你不控制它们。也许一个人跑了,另一个人走了。也许有一个人先用浴室。这与创建两个执行网络请求的按钮没有区别。哪一个完成了?您不知道,因为您不控制网络响应时间。 –

+0

误解是期望如果按钮1被点击后不久按钮2被点击,那么第一个响应应该返回,然后是第二个响应。这与先请人1先给我一支钢笔,然后让人2在不久之后让我拿一支钢笔,并期待人1先给我一支钢笔,这没什么两样。 –

+0

让我们对您的示例进行一些小改动。一个人有一支红笔,另一个人有一支蓝笔。你让这两个人把一支笔放在你的桌子上,但是如果你已经有一支笔想要它被替换。 – Vanuan