Node.js 面试题详细答案 - Q4
Q4: 如何理解 Node.js 的"单线程"?它是如何处理高并发的?
Node.js 单线程的理解
主线程是单线程的
console.log('主线程开始')
for (let i = 0; i < 1000000; i++) {
}
console.log('主线程结束')
但 Node.js 不是完全单线程的
const fs = require('fs')
const crypto = require('crypto')
fs.readFile('large-file.txt', (err, data) => {
console.log('文件读取完成')
})
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', (err, derivedKey) => {
console.log('加密完成')
})
console.log('主线程继续执行')
高并发处理机制
1. 事件循环 + 非阻塞 I/O
const http = require('http')
const server = http.createServer((req, res) => {
setTimeout(() => {
res.end(`请求 ${req.url} 处理完成`)
}, 100)
})
server.listen(3000, () => {
console.log('服务器启动,可以处理大量并发请求')
})
2. 连接处理示例
const http = require('http')
let connectionCount = 0
const server = http.createServer((req, res) => {
connectionCount++
console.log(`当前连接数: ${connectionCount}`)
setTimeout(() => {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(
JSON.stringify({
message: 'Hello World',
connectionId: connectionCount,
})
)
}, 50)
})
server.listen(3000, () => {
console.log('服务器启动在端口 3000')
})
线程池的使用
查看线程池信息
const os = require('os')
console.log('CPU 核心数:', os.cpus().length)
console.log('线程池大小:', process.env.UV_THREADPOOL_SIZE || 4)
线程池操作示例
const fs = require('fs')
const crypto = require('crypto')
const operations = [
() => fs.readFile('file1.txt', () => console.log('文件1读取完成')),
() => fs.readFile('file2.txt', () => console.log('文件2读取完成')),
() =>
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', () =>
console.log('加密1完成')
),
() =>
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', () =>
console.log('加密2完成')
),
() =>
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', () =>
console.log('加密3完成')
),
]
operations.forEach((op) => op())
console.log('主线程继续执行')
高并发 vs 多线程
传统多线程模型
function handleRequest(request) {
const thread = new Thread(() => {
processRequest(request)
})
thread.start()
}
Node.js 单线程模型
const server = http.createServer((req, res) => {
processRequestAsync(req, res)
})
性能对比示例
const http = require('http')
const server = http.createServer((req, res) => {
setTimeout(() => {
res.end('OK')
}, 10)
})
server.listen(3000, () => {
console.log('服务器启动')
for (let i = 0; i < 1000; i++) {
const req = http.request('http://localhost:3000', (res) => {
console.log(`请求 ${i} 完成`)
})
req.end()
}
})
限制和解决方案
CPU 密集型任务的问题
function cpuIntensiveTask() {
let result = 0
for (let i = 0; i < 1000000000; i++) {
result += i
}
return result
}
function cpuIntensiveTaskAsync(callback) {
let result = 0
let i = 0
function processChunk() {
const start = Date.now()
while (i < 1000000000 && Date.now() - start < 5) {
result += i
i++
}
if (i < 1000000000) {
setImmediate(processChunk)
} else {
callback(result)
}
}
processChunk()
}
使用 Worker Threads
const { Worker, isMainThread, parentPort } = require('worker_threads')
if (isMainThread) {
const worker = new Worker(__filename)
worker.postMessage('start')
worker.on('message', (result) => {
console.log('计算结果:', result)
})
} else {
parentPort.on('message', (msg) => {
let result = 0
for (let i = 0; i < 1000000000; i++) {
result += i
}
parentPort.postMessage(result)
})
}
总结
- 主线程单线程:JavaScript 执行是单线程的
- 线程池:I/O 操作使用线程池处理
- 高并发:通过事件循环和非阻塞 I/O 实现
- 优势:内存占用低,开发简单
- 限制:CPU 密集型任务会阻塞主线程
- 解决方案:任务分解、Worker Threads、集群模式