Skip to content

Commit d07cc75

Browse files
committed
Init code
0 parents  commit d07cc75

File tree

6 files changed

+290
-0
lines changed

6 files changed

+290
-0
lines changed

LICENSE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2013 Manifest Web Design
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
this software and associated documentation files (the "Software"), to deal in
7+
the Software without restriction, including without limitation the rights to
8+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
the Software, and to permit persons to whom the Software is furnished to do so,
10+
subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

index.js

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"use strict";
2+
/**
3+
* 使用 Web Worker 实现的线程池
4+
*/
5+
class ThreadPool {
6+
/**
7+
* 创建一个线程池
8+
* @param fn 线程池要执行的函数,它不可带有任何闭包变量,且只能使用有限的函数。
9+
* 详见 https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope
10+
* @param size 线程个数(最大并发数,必须为大于 0 的整数)
11+
*/
12+
constructor(fn, size = navigator.hardwareConcurrency - 1) {
13+
this.queue = [];
14+
if (size < 1)
15+
throw new RangeError('size must greater than 0');
16+
const workerContext = 'data:text/javascript,' + encodeURIComponent(`'use strict';
17+
const __fn = (${fn});
18+
onmessage = e => postMessage(__fn(...e.data));`);
19+
this.freeWorkers = Array.from({ length: size }, () => new Worker(workerContext));
20+
this.workers = new Set(this.freeWorkers);
21+
}
22+
/**
23+
* 当有线程空余时,将参数转发至线程,开始执行。
24+
* 当没有线程空余时,将参数追加至调度队列,等待其他线程空余。
25+
* @param args 传入线程函数的参数。注意它们会以结构化克隆的方式传入(类似深拷贝),而非通常的引用传值。
26+
* https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
27+
* @returns Promise,当线程函数执行完毕后 resolve 其返回值
28+
*/
29+
dispatch(...args) {
30+
return new Promise((resolve, reject) => this.start(resolve, reject, args));
31+
}
32+
/**
33+
* 立即结束所有线程,释放资源。
34+
* 注意:本函数会强制停止正在运行中的线程,并 reject 所有等待中的 promise
35+
*/
36+
dispose() {
37+
this.freeWorkers.forEach(x => {
38+
this.workers.delete(x);
39+
x.terminate();
40+
});
41+
this.queue.forEach(([, reject]) => reject(new TypeError('threadpool disposed')));
42+
this.queue.length = 0;
43+
this.workers.forEach(x => {
44+
x.terminate();
45+
x.onerror(new ErrorEvent('error', { error: new TypeError('threadpool disposed') }));
46+
});
47+
this.workers.clear();
48+
this.freeWorkers.length = 0;
49+
}
50+
/**
51+
* 获得当前空闲的线程个数
52+
*/
53+
getFreeWorkerCount() {
54+
return this.freeWorkers.length;
55+
}
56+
/**
57+
* 获得当前运行中的线程个数
58+
*/
59+
getRunningWorkerCount() {
60+
return this.workers.size - this.freeWorkers.length;
61+
}
62+
/**
63+
* 获得当前在队列中等待的事件个数
64+
*/
65+
getWaitingEventCount() {
66+
return this.queue.length;
67+
}
68+
/// 私有方法
69+
onFinish(worker) {
70+
worker.onmessage = null;
71+
worker.onerror = null;
72+
this.freeWorkers.push(worker);
73+
if (this.queue.length) {
74+
this.start(...this.queue.shift());
75+
}
76+
}
77+
start(resolve, reject, args) {
78+
if (this.freeWorkers.length) {
79+
const worker = this.freeWorkers.pop();
80+
worker.onmessage = e => {
81+
this.onFinish(worker);
82+
resolve(e.data);
83+
};
84+
worker.onerror = e => {
85+
this.onFinish(worker);
86+
reject(e.error);
87+
};
88+
worker.postMessage(args);
89+
}
90+
else {
91+
this.queue.push([resolve, reject, args]);
92+
}
93+
}
94+
}

index.ts

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* 使用 Web Worker 实现的线程池
3+
*/
4+
class ThreadPool {
5+
private readonly workers: Set<Worker>;
6+
private readonly freeWorkers: Worker[];
7+
private readonly queue: [(value: any) => void, (reason: any) => void, any[]][] = [];
8+
9+
/**
10+
* 创建一个线程池
11+
* @param fn 线程池要执行的函数,它不可带有任何闭包变量,且只能使用有限的函数。
12+
* 详见 https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope
13+
* @param size 线程个数(最大并发数,必须为大于 0 的整数)
14+
*/
15+
constructor(fn: Function | string, size = navigator.hardwareConcurrency - 1) {
16+
if (size < 1) throw new RangeError('size must greater than 0');
17+
18+
const workerContext = 'data:text/javascript,' + encodeURIComponent(`'use strict';
19+
const __fn = (${fn});
20+
onmessage = e => postMessage(__fn(...e.data));`
21+
);
22+
23+
this.freeWorkers = Array.from({ length: size }, () => new Worker(workerContext))
24+
this.workers = new Set(this.freeWorkers);
25+
}
26+
27+
/**
28+
* 当有线程空余时,将参数转发至线程,开始执行。
29+
* 当没有线程空余时,将参数追加至调度队列,等待其他线程空余。
30+
* @param args 传入线程函数的参数。注意它们会以结构化克隆的方式传入(类似深拷贝),而非通常的引用传值。
31+
* https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
32+
* @returns Promise,当线程函数执行完毕后 resolve 其返回值
33+
*/
34+
dispatch(...args: any[]) {
35+
return new Promise<any>((resolve, reject) => this.start(resolve, reject, args));
36+
}
37+
38+
/**
39+
* 立即结束所有线程,释放资源。
40+
* 注意:本函数会强制停止正在运行中的线程,并 reject 所有等待中的 promise
41+
*/
42+
dispose() {
43+
this.freeWorkers.forEach(x => {
44+
this.workers.delete(x);
45+
x.terminate();
46+
});
47+
48+
this.queue.forEach(([, reject]) => reject(new TypeError('threadpool disposed')));
49+
this.queue.length = 0;
50+
51+
this.workers.forEach(x => {
52+
x.terminate();
53+
x.onerror(new ErrorEvent('error', { error: new TypeError('threadpool disposed') }))
54+
});
55+
this.workers.clear();
56+
57+
this.freeWorkers.length = 0;
58+
}
59+
60+
/**
61+
* 获得当前空闲的线程个数
62+
*/
63+
getFreeWorkerCount() {
64+
return this.freeWorkers.length;
65+
}
66+
67+
/**
68+
* 获得当前运行中的线程个数
69+
*/
70+
getRunningWorkerCount() {
71+
return this.workers.size - this.freeWorkers.length;
72+
}
73+
74+
/**
75+
* 获得当前在队列中等待的事件个数
76+
*/
77+
getWaitingEventCount() {
78+
return this.queue.length;
79+
}
80+
81+
/// 私有方法
82+
private onFinish(worker: Worker) {
83+
worker.onmessage = null as any;
84+
worker.onerror = null as any;
85+
this.freeWorkers.push(worker);
86+
87+
if (this.queue.length) {
88+
this.start(...this.queue.shift());
89+
}
90+
}
91+
92+
private start(resolve: (value: any) => void, reject: (reason: any) => void, args: any[]) {
93+
if (this.freeWorkers.length) {
94+
const worker = this.freeWorkers.pop() as Worker;
95+
worker.onmessage = e => {
96+
this.onFinish(worker);
97+
resolve(e.data);
98+
};
99+
worker.onerror = e => {
100+
this.onFinish(worker);
101+
reject(e.error);
102+
};
103+
worker.postMessage(args);
104+
} else {
105+
this.queue.push([resolve, reject, args]);
106+
}
107+
}
108+
}

package.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "threadpool.js",
3+
"version": "1.0.0",
4+
"description": "A simple non-blocking Promise implementation using Web Worker",
5+
"main": "index.js",
6+
"repository": "[email protected]:CarterLi/ThreadPool.js.git",
7+
"author": "李通洲 <[email protected]>",
8+
"license": "MIT"
9+
}

test.html

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!DOCTYPE html>
2+
<html lang="zh">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7+
<title>测试JS线程池</title>
8+
9+
<script src="index.js"></script>
10+
</head>
11+
<body>
12+
<ul>
13+
<li>请移动鼠标。如果持续有文字输出,证明后台任务没有阻塞 UI</li>
14+
</ul>
15+
<script>
16+
'use strict';
17+
const arr = Array.from({ length: 819200 }, () => Math.random() * 10000);
18+
19+
console.time('all')
20+
const pool = new ThreadPool(arr => {
21+
return arr.sort();
22+
}, 5);
23+
const promises = [];
24+
for (var i=0; i<20; ++i) {
25+
let index = i;
26+
console.time(index);
27+
promises.push(pool.dispatch(arr)
28+
.then(res => console.log(res))
29+
.catch(err => console.error(err))
30+
.then(() => console.timeEnd(index)));
31+
}
32+
Promise.all(promises).then(() => console.timeEnd('all'));
33+
</script>
34+
<script>
35+
'use strict';
36+
const ul = document.querySelector('ul');
37+
document.onmousemove = e => {
38+
const li = document.createElement('li');
39+
li.textContent = `${e.timeStamp | 0}: ${e.screenX}, ${e.screenY}`;
40+
ul.appendChild(li);
41+
li.scrollIntoView();
42+
}
43+
</script>
44+
</body>
45+
</html>

tsconfig.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compileOnSave": true,
3+
"compilerOptions": {
4+
"module": "umd",
5+
"moduleResolution": "node",
6+
"target": "esnext",
7+
"noUnusedLocals": true,
8+
"noFallthroughCasesInSwitch": true,
9+
"noImplicitReturns": true,
10+
"strict": true,
11+
"alwaysStrict": true,
12+
"noUnusedParameters": true
13+
}
14+
}

0 commit comments

Comments
 (0)