Javascript中的线程安全?

Posted

技术标签:

【中文标题】Javascript中的线程安全?【英文标题】:Thread Safety in Javascript? 【发布时间】:2011-01-16 06:01:36 【问题描述】:

我有一个名为 save() 的函数,该函数收集页面上的所有输入,并对服务器执行 AJAX 调用以保存用户工作的状态。

save() 当前在用户单击保存按钮时调用,或执行一些其他需要我们在服务器上具有最新状态的操作(例如从页面生成文档)。

我添加了每隔一段时间自动保存用户工作的功能。首先,我想防止自动保存和用户生成的保存同时运行。所以我们有以下代码(我正在削减大部分代码,这不是 1:1,但应该足以让我们理解这个想法):

var isSaving=false;
var timeoutId;
var timeoutInterval=300000;
function save(showMsg)

  //Don't save if we are already saving.
  if (isSaving)
   
     return;
  
  isSaving=true;
  //disables the autoSave timer so if we are saving via some other method
  //we won't kick off the timer.
  disableAutoSave();

  if (showMsg)  //show a saving popup
  params=CollectParams();
  PerformCallBack(params,endSave,endSaveError);


function endSave()
  
    isSaving=false;
    //hides popup if it's visible

    //Turns auto saving back on so we save x milliseconds after the last save.
    enableAutoSave();

 
function endSaveError()

   alert("Ooops");
   endSave();

function enableAutoSave()

    timeoutId=setTimeOut(function()save(false);,timeoutInterval);

function disableAutoSave()

    cancelTimeOut(timeoutId);

我的问题是这段代码是否安全?主流浏览器一次只允许一个线程执行吗?

我的一个想法是用户单击保存但没有响应会更糟,因为我们正在自动保存(而且我知道如何修改代码来处理这个问题)。有人在这里看到任何其他问题吗?

【问题讨论】:

那么基于此,如果用户点击保存,计时器在保存功能完成之前不会触发? 如果我理解正确,您是在询问用户是否已启动保存,setTimeout 启动的保存是否会输入相同的方法。答案是否定的,它们永远不会同时出现在 save() 方法中。 由于这些信息有些过时,我们该怎么办? javascript 现在有多个线程:developer.mozilla.org/En/Using_web_workers @jmort - 仍然没有问题。 WebWorkers 将脚本文件作为输入,并提供线程之间的通信方式。这个特定的问题不会受到 WebWorkers 的影响。 【参考方案1】:

浏览器中的 JavaScript 是单线程的。您在任何时间点都只会出现在一个功能中。功能将在输入下一个之前完成。您可以依靠这种行为,因此如果您在 save() 函数中,则在当前函数完成之前,您将永远不会再次输入它。

当您有异步服务器请求(或 setTimeouts 或 setIntervals)时,有时会让人感到困惑(但仍然如此),因为这样感觉就像您的函数是 interleaved。他们不是。

在您的情况下,虽然两个 save() 调用不会相互重叠,但您的自动保存和用户保存可能会连续发生。

如果您只想至少每 x 秒进行一次保存,您可以在保存功能上执行 setInterval 并忘记它。我认为不需要 isSaving 标志。

我认为你的代码可以简化很多:

var intervalTime = 300000;
var intervalId = setInterval("save('my message')", intervalTime);
function save(showMsg)

  if (showMsg)  //show a saving popup
  params=CollectParams();
  PerformCallBack(params, endSave, endSaveError);

  // You could even reset your interval now that you know we just saved.
  // Of course, you'll need to know it was a successful save.
  // Doing this will prevent the user clicking save only to have another
  // save bump them in the face right away because an interval comes up.
  clearInterval(intervalId);
  intervalId = setInterval("save('my message')", intervalTime);


function endSave()

    // no need for this method
    alert("I'm done saving!");


function endSaveError()

   alert("Ooops");
   endSave();

【讨论】:

是的,因为我在使用 isSaving 标志的保存函数中进行了异步调用,应该让我阻止其他人进入该函数,直到我的最终保存运行...对吗? @Josh 我稍微更新了关于 isSaving 标志的答案。 setTimeout 所做的只是在未来至少 x 毫秒内安排一个函数调用。这并不意味着它会放弃它正在做的事情并运行您的函数——您的计划保存不会与另一个函数同时运行。 因为 save 对我们的服务器进行异步调用,所以 isSaving 会阻止保存发生,直到调用 EndSave 函数。 @Josh 这是一个有效的推理,因为它是异步的。您是否担心用户会一遍又一遍地按下保存按钮?另一个选项是禁用保存按钮,直到您的回调发生。 当用户启动保存时,我们会抛出一种半透明的面板,阻止用户做任何事情。当用户执行导致保存发生的操作时,我更担心自动保存运行【参考方案2】:

所有主流浏览器在一个页面上仅支持一个 javascript 线程(除非您使用 web workers)。

不过,XHR 请求可以是异步的。但只要您在当前的保存请求返回之前禁用保存功能,一切都会正常进行。

我唯一的建议是确保在发生自动保存时以某种方式向用户指示(禁用保存按钮等)。

【讨论】:

是的,正在考虑如何向用户表明这一点 如果您有 gmail 帐户,您会注意到他们在自动保存草稿时的方法是将“立即保存”按钮变灰并将文本更改为“保存”,然后更改为“已保存” .只有当用户开始对草稿进行更改时,“立即保存”按钮才会返回可点击状态。【参考方案3】:

目前所有主流浏览器都是单线程javascript执行(只是不要使用web workers,因为少数浏览器支持这种技术!),所以这种方法是安全的。

有关大量参考资料,请参阅Is JavaScript Multithreaded?

【讨论】:

Web 工作者的支持程度如何?看起来 Firefox 3.5+ 没有找到显示浏览器支持此功能的列表。 我不完全确定。根据 John Resig(此处 - ejohn.org/blog/web-workers)、FireFox 3.5 和 Safari 4(截至 2009 年 7 月 29 日)的说法。他还提出了构建代码的理由,以便他们在支持时使用网络工作者(如果我没看错的话)。更重要的是,它们似乎是 html 5 规范的一部分 (whatwg.org/specs/web-workers/current-work)【参考方案4】:

对我来说看起来很安全。 Javascript 是单线程的(除非你使用 webworkers)

它的主题并不完全,但 John Resig 的这篇文章涵盖了 javascript 线程和计时器: http://ejohn.org/blog/how-javascript-timers-work/

【讨论】:

【参考方案5】:

我认为您处理它的方式最适合您的情况。通过使用该标志,您可以保证异步调用不重叠。我还必须处理对服务器的异步调用,并且还使用了某种标志来防止重叠。

正如其他人已经指出的那样,JavaScript 是单线程的,但是如果您希望在与服务器的往返过程中发生相同的事情或不会发生,那么异步调用可能会很棘手。

不过,有一件事是我认为您实际上不需要禁用自动保存。如果在用户保存时尝试自动保存,则保存方法将简单地返回并且不会发生任何事情。另一方面,每次激活自动保存时,您都不必要地禁用和重新启用自动保存。我建议更改为 setInterval 然后忘记它。

另外,我是最小化全局变量的坚持者。我可能会像这样重构您的代码:

var saveWork = (function() 
  var isSaving=false;
  var timeoutId;
  var timeoutInterval=300000;
  function endSave()   
      isSaving=false;
      //hides popup if it's visible
  
  function endSaveError() 
     alert("Ooops");
     endSave();
  
  function _save(showMsg) 
    //Don't save if we are already saving.
    if (isSaving)
     
     return;
    
    isSaving=true;

    if (showMsg)  //show a saving popup
    params=CollectParams();
    PerformCallBack(params,endSave,endSaveError);
  
  return 
    save: function(showMsg)  _save(showMsg); ,
    enableAutoSave: function() 
      timeoutId=setInterval(function()_save(false);,timeoutInterval);
    ,
    disableAutoSave: function() 
      cancelTimeOut(timeoutId);
    
  ;
)();

当然,您不必像那样重构它,但就像我说的,我喜欢最小化全局变量。重要的是整个事情应该在每次保存时都可以正常工作,而无需禁用和重新启用自动保存。

编辑:忘记了必须创建一个私有保存函数才能从 enableAutoSave 中引用

【讨论】:

以上是关于Javascript中的线程安全?的主要内容,如果未能解决你的问题,请参考以下文章

PHP中的线程安全或非线程安全是啥?

python中的线程安全和非线程安全的区别

Java多线程线程安全和非线程安全

java中的线程安全

Android中的线程线程安全 & 线程同步

Android中的线程线程安全 & 线程同步