javascript多线程
好吧,在我们开始之前,让我整理一下,承认这篇文章的标题有点耸人听闻! JavaScript实际上并没有多线程功能,因此JavaScript程序员无法做任何改变。 在所有浏览器中(除了Google Chrome浏览器),JavaScript都在单个执行线程中运行,事实就是如此。
但是,我们可以做的是模拟多线程,只要它产生了多线程环境的好处之一: 它使我们可以运行非常密集的代码 。 该代码否则会冻结浏览器并在Firefox中生成那些“无响应脚本”警告之一。
时间不等人
这一切都取决于异步计时器的使用。 当我们在异步计时器中运行重复代码时,我们将给浏览器的脚本解释器时间来处理每次迭代。
实际上, for
迭代器内部的一段代码要求解释器立即执行所有操作:“尽可能快地运行此代码n次。” 但是,异步计时器中的相同代码会将代码分解为细小的离散块。 也就是说,“尽可能快地运行此代码”,然后等待–然后“尽可能快地运行此代码”,依此类推, n次。
诀窍在于,每次迭代中的代码都足够小且足够简单,以使解释器可以在计时器的速度(100或5,000毫秒)内完全处理它。 如果满足该要求,那么整个代码的强度并不重要,因为我们不要求立即全部运行它。
“太强烈”有多强烈?
通常,如果我编写的脚本过于繁琐,我会考虑对其进行重新设计。 如此大的速度下降通常表示代码有问题,或者应用程序设计有更深层次的问题。
但有时并非如此。 有时候,根本无法避免某种特定操作的强度,除非根本不使用JavaScript。
在给定的情况下,这可能是最好的解决方案。 也许应用程序中的某些处理需要移到服务器端,在服务器端,它通常具有真正的线程执行环境(Web服务器)才能具有更大的处理能力。
但是最终您可能会发现,这不是一个选择,而是JavaScript 一定必须能够做某事或被该死。 在开发Firefox扩展程序Dust-Me Selectors时,我就是这种情况。
该扩展程序的核心是能够测试应用于页面CSS选择器,以查看它们是否被实际使用。 其本质是使用Dean Edwards的base2中的matchAll()
方法进行的一组评估:
for(var i=0; i<selectors.length; i++)
{
if(base2.DOM.Document.matchAll
(contentdoc, selectors[i]).length > 0)
{
used ++;
}
else
{
unused ++;
}
}
当然,很简单。 但是matchAll()
本身非常紧张,它具有-解析-评估和评估任何CSS1或CSS2选择器,然后遍历整个DOM树以寻找匹配项的能力。 扩展名针对每个单独的选择器执行此操作 ,其中可能有数千个选择器 。 从表面上看,该过程非常简单,可能会非常密集,以至于整个浏览器在运行时都会冻结。 这就是我们所发现的。
锁定浏览器显然不是一种选择,因此,如果要使其完全正常运行,我们必须找到一种使它正常运行的方法。
一个简单的测试用例
让我们用一个涉及两个迭代级别的简单测试案例来演示该问题。 内部级别故意过分密集,因此我们可以创建竞争条件,而外部级别相当短,以便模拟主代码。 这就是我们拥有的:
function process()
{
var above = 0, below = 0;
for(var i=0; i<200000; i++)
{
if(Math.random() * 2 > 1)
{
above ++;
}
else
{
below ++;
}
}
}
function test1()
{
var result1 = document.getElementById('result1');
var start = new Date().getTime();
for(var i=0; i<200; i++)
{
result1.value = 'time=' +
(new Date().getTime() - start) + ' [i=' + i + ']';
process();
}
result1.value = 'time=' +
(new Date().getTime() - start) + ' [done]';
}
我们以简单的形式开始测试,并获得输出(这是测试代码,而不是生产代码,请原谅我诉诸使用内联事件处理程序):
<form action="">
<fieldset>
<input type="button" value="test1" onclick="test1()" />
<input type="text" id="result1" />
</fieldset>
</form>
现在,让我们在Firefox中运行该代码(在本例中为2 GHz MacBook上的Firefox 3)…并按预期,浏览器UI在运行时冻结(例如,无法按刷新并放弃该过程)。 经过大约90次迭代后,Firefox会生成一个“无响应脚本”警告对话框。
如果我们允许它继续,则在经过90次迭代之后,Firefox会再次生成相同的对话框。
Safari 3和Internet Explorer 6在这方面的行为类似,具有冻结的UI和产生警告对话框的阈值。 在Opera中,没有这样的对话框-它只会继续运行代码直到完成-但浏览器的用户界面同样会冻结,直到任务完成。
显然,我们在实践中不能运行这样的代码。 因此,让我们对其进行重构,并对外部循环使用异步计时器:
function test2()
{
var result2 = document.getElementById('result2');
var start = new Date().getTime();
var i = 0, limit = 200, busy = false;
var processor = setInterval(function()
{
if(!busy)
{
busy = true;
result2.value = 'time=' +
(new Date().getTime() - start) + ' [i=' + i + ']';
process();
if(++i == limit)
{
clearInterval(processor);
result2.value = 'time=' +
(new Date().getTime() - start) + ' [done]';
}
busy = false;
}
}, 100);
}
![](https://i-blog.csdnimg.cn/blog_migrate/53ef0360d36340eb8d13ca454e8b25c4.png)
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
现在让我们再次运行它…这次我们收到完全不同的结果。 当然,该代码需要一段时间才能完成,但是它一直成功地运行到最后,而不会冻结UI,也不会警告脚本过慢。
( busy
标志用于防止计时器实例冲突。如果在下一次迭代到来时我们已经在子流程的中间,我们只需等待下一个迭代,从而确保只有一个子流程一次运行。)
所以你看,虽然我们可以在内部流程做的工作仍然很少, 次数 ,我们可以运行过程现在已经无限:我们基本上可以永远运行外循环,浏览器将不会冻结。
这更像是–我们可以在野外使用它。
你疯了!
我已经听到反对者的声音。 实际上,我可能是一个我自己:您为什么要这样做?什么样的疯子坚持将JavaScript推向从未设计过的所有这些地方? 您的代码太紧张了。 这是工作的错误工具。 如果您必须克服这些麻烦,那么应用程序的设计从根本上是错误的。
我已经提到了一个示例,其中我必须找到一种使繁重的脚本工作的方法。 要么就是这样,要么整个想法都必须放弃。 如果您不满意该答案,那么本文的其余部分也可能不会吸引您。
但是,如果您-或至少是您愿意接受说服,这是另一个真正的例子: 使用JavaScript编写可以在计算机上玩的游戏 。
游戏开始
我在这里谈论的是理解游戏规则所需的代码,然后该代码可以评估情况和战术,以便在该游戏中击败您。 复杂的东西。
为了说明这一点,我将看一个我已经开发了一段时间的项目。 “很少”是指三年 ,其中大部分时间都花在了理论上可以玩的高原上,但由于过于紧张而无法使用……直到我想到这种方法。 该游戏是基于颜色和形状匹配的竞争难题。
总结一下:您可以通过相邻的形状和颜色匹配来实现自己的目标。 例如,如果您从一个绿色三角形开始,则可以移动到任何其他三角形或任何其他绿色形状。 您的目标是到达中间的水晶,然后将其带到棋盘的另一侧,而您的对手也尝试这样做。 您也可以从对手那里偷水晶。
因此,我们有确定运动的逻辑规则,并且还可以看到战术的出现。 例如,为避免让对手到达水晶或从您那里窃取水晶,您可以选择阻止他们的举动,或尝试在他们无法到达的地方完成比赛。
计算机的工作是在任何给定情况下找到最佳移动,因此让我们以摘要伪代码看一下该过程:
function compute()
{
var move = null;
move = tactic1();
if(!move) { move = tactic2(); }
if(!move) { move = tactic3(); }
if(move)
{
doit();
}
else
{
pass();
}
}
我们评估一种策略,如果这能给我们一个很好的行动,那么我们就完成了; 否则,我们将评估另一种策略,依此类推,直到我们采取行动或得出结论,认为没有战略,我们必须通过。
这些战术功能中的每一个都运行一个昂贵的过程,因为它必须评估董事会中的每个职位以及潜在的未来职位,鉴于各种因素,每个职位可能要多次评估。 该示例仅包含三种策略,但在实际游戏中,存在数十种不同的可能性,每种可能性的评估成本都很高。
这些评估中的任何一个单独都可以,但是所有这些评估一起连续运行,会导致过于紧张的过程冻结浏览器。
因此,我所做的就是将主代码拆分为谨慎的任务 ,每个任务都通过switch
语句进行选择,然后使用异步计时器进行迭代。 这样做的逻辑与我小时候曾经选择过的《选择自己的冒险》书相距一百万英里,在那里,每项任务都会实时选择其他任务,直到我们结束为止:
function compute()
{
var move = null;
var busy = false, task = 'init';
var processor = setInterval(function()
{
if(!busy)
{
switch(task)
{
case 'init' :
move = tactic1();
if(move) { task = 'doit'; }
else { task = 'tactic2'; }
busy = false;
break;
case 'tactic2' :
move = tactic2();
if(move) { task = 'doit'; }
else { task = 'tactic3'; }
busy = false;
break;
case 'tactic3' :
move = tactic3();
if(move) { task = 'doit'; }
else { task = 'pass'; }
busy = false;
break;
case 'doit' :
doit();
task = 'final';
busy = false;
break;
case 'pass' :
pass();
task = 'final';
busy = false;
break;
case 'final' :
clearInterval(processor);
busy = false;
break;
}
}
}, 100);
}
该代码比原始代码冗长得多,因此,如果唯一必须减小代码大小,那显然不是可行的方法。
但是我们在这里试图做的是创建一个没有上限的执行环境 ,也就是说,这个过程在复杂性和长度方面没有上限。 这就是我们所做的。
此模式可以无限期扩展,可以执行数百甚至数千个任务。 这可能需要很长的时间来运行,但运行会,只要每一个人的任务是不是太激烈,它会不杀死浏览器中运行。
一条不归路
这种方法的优点也是它的主要缺点:由于内部函数是异步的,因此我们无法从外部函数返回值 。 因此,例如,我们无法做到这一点(或者,虽然可以,但是毫无意义):
function checksomething()
{
var okay = false;
var i = 0, limit = 100, busy = false;
var processor = setInterval(function()
{
if(!busy)
{
busy = true;
if(condition)
{
okay = true;
}
if(++i == limit)
{
clearInterval(processor);
}
busy = false;
}
}, 100);
return okay;
}
该checksomething()
函数将始终返回false
因为内部函数是异步的。 外部函数将在内部函数的第一次迭代发生之前返回!
下一个示例同样毫无意义:
if(++i == limit)
{
clearInterval(processor);
return okay;
}
我们不在外部函数的范围内,因此我们无法从外部函数中返回。 那个返回值无用地消失在以太里。
我们在这里可以做的是从Ajax编码技术中脱颖而出,并使用一个callback
函数(在本示例中,我将其称为“ oncomplete”):
function checksomething(oncomplete)
{
var okay = false;
var i = 0, limit = 100, busy = false;
var processor = setInterval(function()
{
if(!busy)
{
busy = true;
if(condition)
{
okay = true;
}
if(++i == limit)
{
clearInterval(processor);
if(typeof oncomplete == 'function')
{
oncomplete(okay);
}
}
busy = false;
}
}, 100);
}
因此,当我们调用checksomething()
,我们将传递一个匿名函数作为其参数,并在作业完成时使用最终值调用该函数:
checksomething(function(result)
{
alert(result);
});
优雅? 否。但是功能强大吗? 是。 这就是重点。 使用这种技术,我们可以编写原本不可能的脚本。
机器人梦见硅羊吗?
借助我们工具包中的这项技术,我们现在可以处理以前不在可能性范围内JavaScript项目。 我为这种模式开发的游戏具有相当简单的逻辑,因此也具有相当简单的大脑 ,但是对于常规迭代而言,它仍然太多了。 而且还有很多其他的游戏需要更多的影响力!
我的下一个计划是使用此技术来实现JavaScript Chess引擎。 国际象棋具有多种可能的方案和策略,导致决策可能要花费极长的时间来计算,比没有这种技术的情况要长得多。 即使要创建最基本的思维机,也需要进行大量的计算,我承认对这种可能性感到非常兴奋。
如果我们能拉出这样的花样,谁在说什么可能呢? 自然语言处理,试探法……也许我们有构建JavaScript的基石!
如果您喜欢阅读这篇文章,您会喜欢Learnable的 ; 向大师学习新鲜技能的地方。 会员可以立即访问所有SitePoint的电子书和交互式在线课程,例如JavaScript JavaScript for the Web 。
本文的评论已关闭。 对JavaScript有疑问吗? 为什么不在我们的论坛上提问呢?
图片来源: Randen L Peterson
javascript多线程