关于JavaScript多线程

你知道Web 2.0是什么吗?如果你只觉得Facebook,Gmail就是Web 2.0那你也太肤浅了些。说Ajax就是Web 2.0还接近一些。
这里不详细探讨Web 2.0是什么,不过我觉得2.0可以认为是两个线程。Ajax核心是异步请求,其实就是前台界面一个线程跑你的代码,然后浏览器在实现XmlHttpRequest的异步版本时不小心开了另一个线程去请求数据。然后这个不小心被人发现并大肆应用,加上互联网带宽增长的,程序变得更肆无忌惮,2.0个线程成就了互联网的新时代——Web 2.0。

进一步想,若是JavaScript干脆支持多线程你说怎么样?Web N.0哈哈。下面这段话告诉你这是不可能的:

You must be “as tall as an NBA player” to hack on threaded systems, and that means most programmers should run away crying. But they don’t. Instead, as with most other sharp tools, the temptation is to show how big one is by picking up the nearest singlethreaded code and jamming it into a multi-threaded embedding, or tempting racecondition fate otherwise. Occasionally the results are infamous, but too often, with only virtual fingers and limbs lost, no one learns.
Threads violate abstractions six ways to Sunday. Mainly by creating race conditions, deadlock hazards, and pessimistic locking overhead. And still they don’t scale up to handle the megacore teraflop future.
So my default answer to questions such as, “When will you add threads to JavaScript?” is: “over your dead body!” —— Brendan Eich

这可不是随便什么人说说的,Brendan Eich那可是Mozilla的CTO,JavaScript的创始人。基本上在可见的将来JavaScript是不打算支持多线程的。

这并不是说多线程这个金钥匙就关闭了,有很多办法可以实现并行的目的。

Concurrent.Thread

Concurrent.Thread实现了一个树,需要并行的函数注册到Concurrent.Thread里,然后由Concurrent.Thread安排到树中,主线程再进行模拟多线程操作。Concurrent.Thread库很大,实际中应该加载时性能不很好。
以下文字引用自InfoQ

让我来介绍一下Concurrent.Thread,它是一个允许JavaScript进行多线程编程的库,应用它可以大大缓解上文提及的在AJAX开发中与异步通信相关的困难。这是一个用JavaScript写成的免费的软件库,使用它的前提是遵守Mozilla Public License和GNU General Public License这两个协议。你可以从他们的网站下载源代码。

马上来下载和使用源码吧!假定你已经将下载的源码保存到一个名为Concurrent.Thread.js的文件夹里,在进行任何操作之前,先运行如下程序,这是一个很简单的功能实现:

[javascript]
Concurrent.Thread.create
(function()
{
var i = 0;
while (1) { document.body.innerHTML += i++ + “
“; }
});
[/javascript]

执行这个程序将会顺序显示从0开始的数字,它们一个接一个出现,你可以滚屏来看它。现在让我们来仔细研究一下代码,他应用while(1)条件制造了一个不会中止的循环,通常情况下,象这样不断使用一个并且是唯一一个线程的JavaScript程序会导致浏览器看起来象冻结了一样,自然也就不会允许你滚屏。那么为什么上面的这段程序允许你这么做呢?关键之处在于while(1)上面的那条Concurrent.Thread.create()语句,这是这个库提供的一个方法,它可以创建一个新线程。被当做参数传入的函数在这个新线程里执行,让我们对程序做如下微调:

[code=’javascript’]
function f (i)
{
while (1) { document.body.innerHTML += i++ + “
“; }
}

Concurrent.Thread.create(f, 0);
Concurrent.Thread.create(f, 100000);
[/code]

在这个程序里有个新函数f()可以重复显示数字,它是在程序段起始定义的,接着以f()为参数调用了两次create()方法,传给create()方法的第二个参数将会不加修改地传给f()。执行这个程序,先会看到一些从0开始的小数,接着是一些从100,000开始的大数,然后又是接着前面小数顺序的数字。你可以观察到程序在交替显示小数和大数,这说明两个线程在同时运行。

让我来展示Concurrent.Thread的另外一个用法。上面的例子调用create()方法来创建新线程。不调用库里的任何APIs也有可能实现这个目的。例如,前面那个例子可以这样写:

[code=’html’]


[/code]

在script 标签内,很简单地用JavaScript写了一个无穷循环。你应该注意到标签内的type属性,那里是一个很陌生的值(text/x-script.multithreaded-js),如果这个属性被放在script标签内,那么Concurrent.Thread就会在一个新的线程内执行标签之间的程序。你应当记住一点,在本例一样,必须将Concurrent.Thread库包含进来。

有了Concurrent.Thread,就有可能自如的将执行环境在线程之间进行切换,即使你的程序很长、连续性很强。我们可以简要地讨论下如何执行这种操作。简言之,需要进行代码转换。粗略地讲,首先要把传递给create()的函数转换成一个字符串,接着改写直至它可以被分批分次执行。然后这些程序可以依照调度程序逐步执行。调度程序负责协调多线程,换句话说,它可以在适当的时候做出调整以便每一个修改后的函数都会得到同等机会运行。 Concurrent.Thread实际上并没有创建新的线程,仅仅是在原本单线程的基础上模拟了一个多线程环境。

虽然转换后的函数看起来是运行在不同的线程内,但是实际上只有一个线程在做这所有的事情。在转换后的函数内执行同步通信仍然会造成浏览器冻结,你也许会认为以前的那些问题根本就没有解决。不过你不必耽心,Concurrent.Thread提供了一个应用JavaScript 的异步通信方式实现的定制通信库,它被设计成当一个线程在等待服务器的响应时允许其它线程运行。这个通信库存于 Concurrent.Thread.Http下。它的用法如下所示:

[code=’html’]


[/code]

get()方法,就像它的名字暗示的那样,可以通过HTTP的GET方法获得指定URL的内容,它将目标URL作为第一个参数,将一个代表HTTP请求头的数组作为可选的第二个参数。get()方法与服务器交互,当得到服务器的响应后就返回一个XMLHttpRequest对象作为返回值。当get()方法返回时,已经收到了服务器响应,所以就没必要再用回调函数接收结果。自然,也不必再耽心当程序等待服务器的响应时浏览器冻结的情况了。另外,还有一个 post()方法可以用来发送数据到服务器:

[code=’html’]


[/code]

post()方法将目的URL作为第一个参数,要发送的内容作为第二个参数。像get()方法那样,你也可以将请求头作为可选的第三个参数。

Simulating

第二种方法是比较简单的模拟方式,也是通过一个线程去模拟多线程并行执行的过程。以下是库的实现:
[code=’javascript’]
//loops through an array in segments
var threadedLoop = function(array) {
var self = this;

//holds the threaded work
var thread = {
work: null,
wait: null,
index: 0,
total: array.length,
finished: false
};

//set the properties for the class
this.collection = array;
this.finish = function() { };
this.action = function() { throw “You must provide the action to do for each element”; };
this.interval = 1;

//set this to public so it can be changed
var chunk = parseInt(thread.total * .005);
this.chunk = (chunk == NaN || chunk == 0) ? thread.total : chunk;

//end the thread interval
thread.clear = function() {
window.clearInterval(thread.work);
window.clearTimeout(thread.wait);
thread.work = null;
thread.wait = null;
};

//checks to run the finish method
thread.end = function() {
if (thread.finished) { return; }
self.finish();
thread.finished = true;
};

//set the function that handles the work
thread.process = function() {
if (thread.index >= thread.total) { return false; }

//thread, do a chunk of the work
if (thread.work) {
var part = Math.min((thread.index + self.chunk), thread.total);
while (thread.index++ < part) { self.action(self.collection[thread.index], thread.index, thread.total); } } else { //no thread, just finish the work while(thread.index++ < thread.total) { self.action(self.collection[thread.index], thread.index, thread.total); } } //check for the end of the thread if (thread.index >= thread.total) {
thread.clear();
thread.end();
}

//return the process took place
return true;

};

//set the working process
self.start = function() {
thread.finished = false;
thread.index = 0;
thread.work = window.setInterval(thread.process, self.interval);
};

//stop threading and finish the work
self.wait = function(timeout) {

//create the waiting function
var complete = function() {
thread.clear();
thread.process();
thread.end();
};

//if there is no time, just run it now
if (!timeout) {
complete();
}
else {
thread.wait = window.setTimeout(complete, timeout);
}
};

};

// Note: this class is not battle-tested, just personal testing on large arrays
[/code]

搞点测试数据:
[code=’javascript’]
var array = [];
for (var i = 0; i < 500000; i++) { array.push("this is some long string"); } [/code] 启动并行执行: [code='javascript'] //create our new class var work = new threadedLoop(array); //create the action to compare each item with work.action = function(item, index, total) { var check = (item == "this is some long string comparison to slow it down"); document.body.innerHTML = "Item " + index + " of " + total; }; //another action to use when our loop is done work.finish = function(thread) { alert("Thread finished!"); }; //and start our 'thread' work.start(); [/code]

GIF Image Hack

这种方法是在国内网站见到的,蛮好玩的。原理就是搞一张gif图片,能动画的,会自动循环的那种。然后利用每次动画结束后的onload事件启动多个函数的执行。

[code=’javascript’]
var img = new Image();
img.src = “images/animation.gif”;
img.onload = function()
{
alert(“如要关闭请按住ESC键不放,并点击关闭按钮”);
}
[/code]

参考资料

  1. InfoQ: JavaScript多线程编程简介
  2. javascript多线程的实现方法
  3. Simulate Threading Using Javascript

发表评论