关于多线程:JavaScript和线程

关于多线程:JavaScript和线程

JavaScript and Threads

有没有办法在JavaScript中进行多线程?


有关最新的支持信息,请访问http://caniuse.com/#search=worker。

以下是大约2009年的支持状况。

你想谷歌的词是JavaScript工作者线程

除了Gears之外,目前还没有什么可用,但是有很多关于如何实现这一点的讨论,所以我想看看这个问题,因为答案无疑会在未来发生变化。

以下是Gears:WorkerPool API的相关文档

WHATWG有一份关于工作者线程的建议草案:Web Workers

还有Mozilla的DOM Worker Threads

更新:2009年6月,当前浏览器支持JavaScript线程的状态

Firefox 3.5有网络工作者。一些网络工作者的演示,如果你想看到他们的行动:

  • 模拟退火("试一试"链接)
  • 太空入侵者(帖子末尾的链接)
  • MoonBat JavaScript Benchmark(第一个链接)

Gears插件也可以安装在Firefox中。

Safari 4和WebKit nightlies都有工作线程:

  • JavaScript Ray Tracer

Chrome有Gears,所以它可以做线程,虽然它需要用户的确认提示(它使用不同的API到Web工作者,虽然它可以在安装了Gears插件的任何浏览器中工作):

  • 谷歌Gears WorkerPool演示(不是一个很好的例子,因为它运行速度太快,无法在Chrome和Firefox中进行测试,尽管IE运行得足够慢以至于阻止了交互)

IE8和IE9只能安装Gears插件的线程


在JavaScript中进行多线程和异步的不同方法

在HTML5之前,JavaScript只允许每页执行一个线程。

使用Yield,setTimeout()setInterval()XMLHttpRequest或事件处理程序模拟异步执行有一些hacky方法(有关yield和setTimeout()的示例,请参阅本文末尾)。

但是使用HTML5,我们现在可以使用工作线程来并行执行函数。这是一个使用示例。

真正的多线程

多线程:JavaScript工作线程

HTML5引入了Web Worker Threads(参见:浏览器兼容性)
注意:IE9及更早版本不支持它。

这些工作线程是在后台运行的JavaScript线程,不会影响页面的性能。有关Web Worker的更多信息,请阅读文档或本教程。

下面是一个简单的示例,其中3个Web Worker线程计数到MAX_VALUE并在页面中显示当前计算的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackoverflow.com/a/16799132/2576706
function getScriptPath(foo){ return window.URL.createObjectURL(new Blob([foo.toString().match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1]],{type:'text/javascript'})); }

var MAX_VALUE = 10000;

/*
 *  Here are the workers
 */

//Worker 1
var worker1 = new Worker(getScriptPath(function(){
    self.addEventListener('message', function(e) {
        var value = 0;
        while(value <= e.data){
            self.postMessage(value);
            value++;
        }
    }, false);
}));
//We add a listener to the worker to get the response and show it in the page
worker1.addEventListener('message', function(e) {
  document.getElementById("result1").innerHTML = e.data;
}, false);


//Worker 2
var worker2 = new Worker(getScriptPath(function(){
    self.addEventListener('message', function(e) {
        var value = 0;
        while(value <= e.data){
            self.postMessage(value);
            value++;
        }
    }, false);
}));
worker2.addEventListener('message', function(e) {
  document.getElementById("result2").innerHTML = e.data;
}, false);


//Worker 3
var worker3 = new Worker(getScriptPath(function(){
    self.addEventListener('message', function(e) {
        var value = 0;
        while(value <= e.data){
            self.postMessage(value);
            value++;
        }
    }, false);
}));
worker3.addEventListener('message', function(e) {
    document.getElementById("result3").innerHTML = e.data;
}, false);


// Start and send data to our worker.
worker1.postMessage(MAX_VALUE);
worker2.postMessage(MAX_VALUE);
worker3.postMessage(MAX_VALUE);
1
 

我们可以看到三个线程以并发方式执行,并在页面中打印它们的当前值。它们不会冻结页面,因为它们是在后台以分离的线程执行的。

多线程:具有多个iframe

实现此目的的另一种方法是使用多个iframe,每个iframe将执行一个线程。我们可以通过URL为iframe提供一些参数,iframe可以与其父级进行通信,以获得结果并将其打印回来(iframe必须位于同一个域中)。

此示例不适用于所有浏览器! iframe通常在与主页面相同的线程/进程中运行(但Firefox和Chromium似乎以不同方式处理它)。

由于代码段不支持多个HTML文件,因此我将在此处提供不同的代码:

index.html的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//The 3 iframes containing the code (take the thread id in param)
<iframe id="threadFrame1" src="thread.html?id=1"></iframe>
<iframe id="threadFrame2" src="thread.html?id=2"></iframe>
<iframe id="threadFrame3" src="thread.html?id=3"></iframe>

//Divs that shows the result






    //This function is called by each iframe
    function threadResult(threadId, result) {
        document.getElementById("result" + threadId).innerHTML = result;
    }

thread.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Get the parameters in the URL: http://stackoverflow.com/a/1099670/2576706
function getQueryParams(paramName) {
    var qs = document.location.search.split('+').join(' ');
    var params = {}, tokens, re = /[?&]?([^=]+)=([^&]*)/g;
    while (tokens = re.exec(qs)) {
        params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
    }
    return params[paramName];
}

//The thread code (get the id from the URL, we can pass other parameters as needed)
var MAX_VALUE = 100000;
(function thread() {
    var threadId = getQueryParams('id');
    for(var i=0; i<MAX_VALUE; i++){
        parent.threadResult(threadId, i);
    }
})();

模拟多线程

单线程:使用setTimeout()模拟JavaScript并发

'天真'的方式是一个接一个地执行函数setTimeout(),如下所示:

1
2
3
setTimeout(function(){ /* Some tasks */ }, 0);
setTimeout(function(){ /* Some tasks */ }, 0);
[...]

但是这种方法不起作用,因为每个任务将一个接一个地执行。

我们可以通过递归调用函数来模拟异步执行,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var MAX_VALUE = 10000;

function thread1(value, maxValue){
    var me = this;
    document.getElementById("result1").innerHTML = value;
    value++;
 
    //Continue execution
    if(value<=maxValue)
        setTimeout(function () { me.thread1(value, maxValue); }, 0);
}

function thread2(value, maxValue){
    var me = this;
    document.getElementById("result2").innerHTML = value;
    value++;
   
    if(value<=maxValue)
        setTimeout(function () { me.thread2(value, maxValue); }, 0);
}

function thread3(value, maxValue){
    var me = this;
    document.getElementById("result3").innerHTML = value;
    value++;
   
    if(value<=maxValue)
        setTimeout(function () { me.thread3(value, maxValue); }, 0);
}

thread1(0, MAX_VALUE);
thread2(0, MAX_VALUE);
thread3(0, MAX_VALUE);
1
 

正如您所看到的,第二种方法非常慢并冻结浏览器,因为它使用主线程来执行这些功能。

单线程:使用yield模拟JavaScript并发性

Yield是ECMAScript 6中的一项新功能,它仅适用于最旧版本的Firefox和Chrome(在Chrome中,您需要启用chrome:// flags / #enable-javascript-harmony中的实验性JavaScript)。

The yield keyword causes generator function execution to pause and the value of the expression following the yield keyword is returned to the generator's caller. It can be thought of as a generator-based version of the return keyword.

生成器允许您暂停执行函数并在以后恢复。可以使用生成器通过称为trampolining的技术来安排您的功能。

这是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var MAX_VALUE = 10000;

Scheduler = {
    _tasks: [],
    add: function(func){
        this._tasks.push(func);
    }, 
    start: function(){
        var tasks = this._tasks;
        var length = tasks.length;
        while(length>0){
            for(var i=0; i<length; i++){
                var res = tasks[i].next();
                if(res.done){
                    tasks.splice(i, 1);
                    length--;
                    i--;
                }
            }
        }
    }  
}


function* updateUI(threadID, maxValue) {
  var value = 0;
  while(value<=maxValue){
    yield document.getElementById("result" + threadID).innerHTML = value;
    value++;
  }
}

Scheduler.add(updateUI(1, MAX_VALUE));
Scheduler.add(updateUI(2, MAX_VALUE));
Scheduler.add(updateUI(3, MAX_VALUE));

Scheduler.start()
1
 


使用HTML5"side-specs"不再需要使用setTimeout(),setInterval()等来破解javascript。

HTML5和Friends介绍了javascript Web Workers规范。它是一个异步和独立运行脚本的API。

指向规范和教程的链接。


JavaScript中没有真正的线程。 JavaScript是可塑的语言,它允许你模仿它的一部分。这是我前几天遇到的一个例子。


Javascript中没有真正的多线程,但您可以使用setTimeout()和异步AJAX请求获得异步行为。

你到底想要完成什么?


这里只是一种在Javascript中模拟多线程的方法

现在我将创建3个线程,它们将计算数字加法,数字可以除以13,数字可以除以3到10000000000.这三个函数不能像Concurrency那样同时运行。但是我将向您展示一个技巧,使这些函数在同一时间递归运行:jsFiddle

这段代码属于我。

身体的一部分

1
2
3
4
5
6
7
    <input type="button" value="start/stop" onclick="_thread1.control ? _thread1.stop() : _thread1.start();" /><span>Counting summation of numbers till 10000000000</span> = <span id="1">0</span>


    <input type="button" value="start/stop" onclick="_thread2.control ? _thread2.stop() : _thread2.start();" /><span>Counting numbers can be divided with 13 till 10000000000</span> = <span id="2">0</span>


    <input type="button" value="start/stop" onclick="_thread3.control ? _thread3.stop() : _thread3.start();" /><span>Counting numbers can be divided with 3 till 10000000000</span> = <span id="3">0</span>

Javascript部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
var _thread1 = {//This is my thread as object
    control: false,//this is my control that will be used for start stop
    value: 0, //stores my result
    current: 0, //stores current number
    func: function () {   //this is my func that will run
        if (this.control) {      // checking for control to run
            if (this.current < 10000000000) {
                this.value += this.current;  
                document.getElementById("1").innerHTML = this.value;
                this.current++;
            }
        }
        setTimeout(function () {  // And here is the trick! setTimeout is a king that will help us simulate threading in javascript
            _thread1.func();    //You cannot use this.func() just try to call with your object name
        }, 0);
    },
    start: function () {
        this.control = true;   //start function
    },
    stop: function () {
        this.control = false;    //stop function
    },
    init: function () {
        setTimeout(function () {
            _thread1.func();    // the first call of our thread
        }, 0)
    }
};
var _thread2 = {
    control: false,
    value: 0,
    current: 0,
    func: function () {
        if (this.control) {
            if (this.current % 13 == 0) {
                this.value++;
            }
            this.current++;
            document.getElementById("2").innerHTML = this.value;
        }
        setTimeout(function () {
            _thread2.func();
        }, 0);
    },
    start: function () {
        this.control = true;
    },
    stop: function () {
        this.control = false;
    },
    init: function () {
        setTimeout(function () {
            _thread2.func();
        }, 0)
    }
};
var _thread3 = {
    control: false,
    value: 0,
    current: 0,
    func: function () {
        if (this.control) {
            if (this.current % 3 == 0) {
                this.value++;
            }
            this.current++;
            document.getElementById("3").innerHTML = this.value;
        }
        setTimeout(function () {
            _thread3.func();
        }, 0);
    },
    start: function () {
        this.control = true;
    },
    stop: function () {
        this.control = false;
    },
    init: function () {
        setTimeout(function () {
            _thread3.func();
        }, 0)
    }
};

_thread1.init();
_thread2.init();
_thread3.init();

我希望这种方式会有所帮助。


您可以使用Narrative JavaScript,这是一种将您的代码转换为状态机的编译器,有效地允许您模拟线程。它通过在允许您在单个线性代码块中编写异步代码的语言添加"让步"运算符(标记为" - >")来实现。


另一种可能的方法是在javascript环境中使用javascript解释器。

通过创建多个解释器并从主线程控制它们的执行,您可以模拟多线程,每个线程在自己的环境中运行。

该方法有点类似于Web worker,但您可以让解释器访问浏览器全局环境。

我做了一个小项目来证明这一点。

这篇博客文章中有更详细的解释。


今天应该推出的新款v8引擎支持它(我认为)


如果您不能或不想使用任何AJAX内容,请使用iframe或10! ;)您可以让iframe中的进程与母版页并行运行,而不必担心与dot net AJAX等交叉浏览器可比较的问题或语法问题,您可以从一个调用母版页的JavaScript(包括它导入的JavaScript) iframe中。

例如,在父iframe中,一旦iframe内容加载(即异步部分),就在父文档中调用egFunction()

1
parent.egFunction();

动态生成iframe,如果你愿意,主html代码可以免费使用。


在原始的Javascript中,你能做的最好的就是使用少量异步调用(xmlhttprequest),但这并不是真正的线程,而且非常有限。 Google Gears在浏览器中添加了许多API,其中一些可用于线程支持。


Javascript没有线程,但我们确实有工作人员。

如果您不需要共享对象,工作人员可能是一个不错的选择。

大多数浏览器实现实际上将工作人员分布在所有核心上,允许您使用所有核心你可以在这里看到这个的演示。

我开发了一个名为task.js的库,这使得这很容易做到。

task.js Simplified interface for getting CPU intensive code to run on all cores (node.js, and web)

一个例子是

1
2
3
4
5
6
7
8
9
10
11
function blocking (exampleArgument) {
    // block thread
}

// turn blocking pure function into a worker task
const blockingAsync = task.wrap(blocking);

// run task on a autoscaling worker pool
blockingAsync('exampleArgumentValue').then(result => {
    // do something with result
});

使用HTML5规范,您不需要为此编写太多的JS或找到一些黑客。

HTML5中引入的功能之一是Web Workers,它是在后台运行的JavaScript,独立于其他脚本,而不会影响页面的性能。

几乎所有浏览器都支持它:

Chrome - 4.0+

IE - 10.0+

Mozilla - 3.5+

Safari - 4.0+

歌剧 - 11.5+


推荐阅读