Skip to content

Commit 508e69c

Browse files
committed
blog: add new blog
1 parent 6033335 commit 508e69c

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

blog/2025-08-19-eslint.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
---
2+
title: eslint 支持多线程并发 Lint
3+
slug: /eslint-new-concurrency
4+
authors: oxygen
5+
description: eslint 支持多线程运行
6+
---
7+
8+
ESLint 于 2025/08/15 日合并了一个 PR:[feat: multithread linting](https://github.com/eslint/eslint/pull/19794),这个 PR 解决了一个[长达十年之久的 issue](https://github.com/eslint/eslint/issues/3565),是一个非常大的优化项。下面一起来看下这个 PR 改动了什么。
9+
10+
<!--truncate-->
11+
12+
## PR 改动
13+
14+
[feat: multithread linting](https://github.com/eslint/eslint/pull/19794) 这个 PR 改动的文件中,主要改动如下:
15+
16+
1. [ESLint nodejs api](https://eslint.org/docs/latest/integrate/nodejs-api) 新增 `concurrency` 属性;
17+
2. [ESLint CLI](https://eslint.org/docs/latest/use/command-line-interface) 新增 `concurrency` 参数;
18+
3. `concurrency` 可以设置为 `off``auto` 和一个数字;默认值为 `off`,也就是默认不启动多线程。
19+
20+
根据这个 PR 开发者的描述,启动多线程可以让 ESLint 在大型项目多个文件的速度表现上提升 30% 以上,虽然比不上用 Rust 或者 Go 编写的下一代 Lint 工具,但是相较于 ESLint 本身是非常大的提升了。
21+
22+
## 何时可用
23+
24+
截止到这篇文章的时间,ESLint 目前最新的版本 `9.33.0` 尚未包含这个 PR 改动的内容,我估计等下个版本 ESLint 就会包含这项改动了,也就是 `9.34.0` 版本。
25+
26+
## ESLint 是如何计算 worker 线程数的
27+
28+
根据 PR 改动的代码细节,找到了 `lib/eslint/eslint.js` 这个文件中根据设置的 `concurrency` 对 worker 线程计算的方法,[在 287 行](https://github.com/eslint/eslint/blob/676f4acaaed6e4f6ffe0c2e21272d4702b311a7b/lib/eslint/eslint.js#L287)
29+
30+
1. 当配置 `concurrency: auto` 时,会使用 Node 的 `os.availableParallelism()` 方法获取进程真正可用的最大并行度,会考虑**容器限制**(Docker、K8s 限制 CPU quota 的情况);
31+
2. 当配置 `concurrency` 为一个数字时,会对比 `concurrency` 和 lint 文件数量,去最小值;
32+
3. 当计算出使用多少个 worker 时,就会使用 `node:worker_threads` 模块的 `Worker` 创建多个 `worker` 实例。
33+
34+
```typescript
35+
/**
36+
* 计算使用多少个 worker
37+
* @param {number | "auto" | "off"} concurrency The normalized concurrency setting.
38+
* @param {number} fileCount The number of files to be linted.
39+
* @param {{ availableParallelism: () => number }} [os] Node.js `os` module, or a mock for testing.
40+
* @returns {number} The effective number of worker threads to be started. A value of zero disables multithread linting.
41+
*/
42+
function calculateWorkerCount(
43+
concurrency,
44+
fileCount,
45+
{ availableParallelism } = os,
46+
) {
47+
let workerCount;
48+
switch (concurrency) {
49+
case "off":
50+
return 0;
51+
case "auto": {
52+
workerCount = Math.min(
53+
availableParallelism() >> 1,
54+
Math.ceil(fileCount / AUTO_FILES_PER_WORKER),
55+
);
56+
break;
57+
}
58+
default:
59+
workerCount = Math.min(concurrency, fileCount);
60+
break;
61+
}
62+
return workerCount > 1 ? workerCount : 0;
63+
}
64+
65+
/**
66+
* The smallest net linting ratio that doesn't trigger a poor concurrency warning.
67+
* The net linting ratio is defined as the net linting duration divided by the thread's total runtime,
68+
* where the net linting duration is the total linting time minus the time spent on I/O-intensive operations:
69+
* **Net Linting Ratio** = (**Linting Time** – **I/O Time**) / **Thread Runtime**.
70+
* - **Linting Time**: Total time spent linting files
71+
* - **I/O Time**: Portion of linting time spent loading configs and reading files
72+
* - **Thread Runtime**: End-to-end execution time of the thread
73+
*
74+
* This value is a heuristic estimation that can be adjusted if required.
75+
*/
76+
const LOW_NET_LINTING_RATIO = 0.7;
77+
78+
/**
79+
* 根据计算的 workerCount 创建多个 Worker 实例来实现多线程 Lint
80+
*/
81+
async function runWorkers(
82+
filePaths,
83+
workerCount,
84+
eslintOptionsOrURL,
85+
warnOnLowNetLintingRatio,
86+
) {
87+
const fileCount = filePaths.length;
88+
const results = Array(fileCount);
89+
const workerURL = pathToFileURL(path.join(__dirname, "./worker.js"));
90+
const filePathIndexArray = new Uint32Array(
91+
new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT),
92+
);
93+
const abortController = new AbortController();
94+
const abortSignal = abortController.signal;
95+
const workerOptions = {
96+
env: SHARE_ENV,
97+
workerData: {
98+
eslintOptionsOrURL,
99+
filePathIndexArray,
100+
filePaths,
101+
},
102+
};
103+
104+
const hrtimeBigint = process.hrtime.bigint;
105+
let worstNetLintingRatio = 1;
106+
107+
/**
108+
* A promise executor function that starts a worker thread on each invocation.
109+
* @param {() => void} resolve_ Called when the worker thread terminates successfully.
110+
* @param {(error: Error) => void} reject Called when the worker thread terminates with an error.
111+
* @returns {void}
112+
*/
113+
function workerExecutor(resolve_, reject) {
114+
const workerStartTime = hrtimeBigint();
115+
const worker = new Worker(workerURL, workerOptions);
116+
worker.once(
117+
"message",
118+
(/** @type {WorkerLintResults} */ indexedResults) => {
119+
const workerDuration = hrtimeBigint() - workerStartTime;
120+
121+
// The net linting ratio provides an approximate measure of worker thread efficiency, defined as the net linting duration divided by the thread's total runtime.
122+
const netLintingRatio =
123+
Number(indexedResults.netLintingDuration) /
124+
Number(workerDuration);
125+
126+
worstNetLintingRatio = Math.min(
127+
worstNetLintingRatio,
128+
netLintingRatio,
129+
);
130+
for (const result of indexedResults) {
131+
const { index } = result;
132+
delete result.index;
133+
results[index] = result;
134+
}
135+
resolve_();
136+
},
137+
);
138+
worker.once("error", error => {
139+
abortController.abort(error);
140+
reject(error);
141+
});
142+
abortSignal.addEventListener("abort", () => worker.terminate());
143+
}
144+
145+
// 使用 workerCount 创建 worker
146+
const promises = Array(workerCount);
147+
for (let index = 0; index < workerCount; ++index) {
148+
promises[index] = new Promise(workerExecutor);
149+
}
150+
await Promise.all(promises);
151+
152+
if (worstNetLintingRatio < LOW_NET_LINTING_RATIO) {
153+
warnOnLowNetLintingRatio();
154+
}
155+
156+
return results;
157+
}
158+
```
159+
160+
## 是否无脑开启多线程
161+
162+
`concurrency` 默认关闭,也就是你不应该无脑开启多线程,因为多线程的开销(创建/销毁 worker + 进程间通信)等也会增加 Lint 时长。
163+
164+
在上面创建 worker 中我们也可以看到 ESLint 会定义一个叫 `netLintingRatio` 的数值,用来计算 worker 的执行效率:
165+
$$
166+
Net Linting Ratio = (Linting Time – I/O Time) / Thread Runtime.
167+
$$
168+
`netLintingRatio` 小于 `LOW_NET_LINTING_RATIO: 0.7`,则表示当前多线程执行效率不高,会输出警告提示信息,提示应该禁用或者降低`concurrency` 的值。
169+
170+
![image-20250820104539519](./../public/images/image-20250820104539519.png)
32.8 KB
Loading

0 commit comments

Comments
 (0)