Node.js 面试题详细答案 - Q5
Q5: 什么是阻塞(Blocking)和非阻塞(Non-Blocking)I/O?请举例说明。
阻塞 I/O (Blocking I/O)
阻塞 I/O 是指当程序执行 I/O 操作时,会等待操作完成才继续执行后续代码。
特点
- 同步执行
- 等待 I/O 完成
- 阻塞主线程
- 简单但效率低
示例
const fs = require('fs')
console.log('开始读取文件')
// 阻塞 I/O - 同步读取文件
try {
const data = fs.readFileSync('large-file.txt', 'utf8')
console.log('文件读取完成,大小:', data.length)
} catch (error) {
console.error('读取文件失败:', error.message)
}
console.log('继续执行其他任务')
// 输出顺序:
// 开始读取文件
// 文件读取完成,大小: 1024
// 继续执行其他任务
阻塞 I/O 的问题
const fs = require('fs')
console.log('任务1开始')
// 阻塞操作 - 会阻塞后续任务
const data1 = fs.readFileSync('file1.txt')
console.log('任务1完成')
console.log('任务2开始')
const data2 = fs.readFileSync('file2.txt')
console.log('任务2完成')
console.log('任务3开始')
const data3 = fs.readFileSync('file3.txt')
console.log('任务3完成')
// 总时间 = 任务1时间 + 任务2时间 + 任务3时间
// 无法并行执行
非阻塞 I/O (Non-Blocking I/O)
非阻塞 I/O 是指当程序执行 I/O 操作时,不会等待操作完成,而是立即返回并继续执行后续代码。
特点
- 异步执行
- 不等待 I/O 完成
- 不阻塞主线程
- 复杂但效率高
示例
const fs = require('fs')
console.log('开始读取文件')
// 非阻塞 I/O - 异步读取文件
fs.readFile('large-file.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取文件失败:', err.message)
return
}
console.log('文件读取完成,大小:', data.length)
})
console.log('继续执行其他任务')
// 输出顺序:
// 开始读取文件
// 继续执行其他任务
// 文件读取完成,大小: 1024
非阻塞 I/O 的优势
const fs = require('fs')
console.log('开始并行任务')
// 非阻塞操作 - 可以并行执行
fs.readFile('file1.txt', (err, data1) => {
console.log('任务1完成')
})
fs.readFile('file2.txt', (err, data2) => {
console.log('任务2完成')
})
fs.readFile('file3.txt', (err, data3) => {
console.log('任务3完成')
})
console.log('所有任务已启动')
// 总时间 ≈ max(任务1时间, 任务2时间, 任务3时间)
// 可以并行执行
对比示例
阻塞 vs 非阻塞
const fs = require('fs')
// 阻塞方式
function blockingExample() {
console.log('=== 阻塞方式 ===')
const start = Date.now()
const data1 = fs.readFileSync('file1.txt')
const data2 = fs.readFileSync('file2.txt')
const data3 = fs.readFileSync('file3.txt')
const end = Date.now()
console.log(`阻塞方式耗时: ${end - start}ms`)
}
// 非阻塞方式
function nonBlockingExample() {
console.log('=== 非阻塞方式 ===')
const start = Date.now()
let completed = 0
function checkComplete() {
completed++
if (completed === 3) {
const end = Date.now()
console.log(`非阻塞方式耗时: ${end - start}ms`)
}
}
fs.readFile('file1.txt', checkComplete)
fs.readFile('file2.txt', checkComplete)
fs.readFile('file3.txt', checkComplete)
}
// 执行对比
blockingExample()
nonBlockingExample()
实际应用场景
HTTP 服务器对比
const http = require('http')
// 阻塞方式 - 每个请求阻塞服务器
const blockingServer = http.createServer((req, res) => {
console.log('处理请求:', req.url)
// 阻塞操作 - 会阻塞其他请求
const data = fs.readFileSync('large-file.txt')
res.end(data)
})
// 非阻塞方式 - 可以处理并发请求
const nonBlockingServer = http.createServer((req, res) => {
console.log('处理请求:', req.url)
// 非阻塞操作 - 不阻塞其他请求
fs.readFile('large-file.txt', (err, data) => {
if (err) {
res.statusCode = 500
res.end('Internal Server Error')
return
}
res.end(data)
})
})
// 非阻塞服务器可以同时处理多个请求
nonBlockingServer.listen(3000)
数据库操作对比
const mysql = require('mysql')
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test',
})
// 阻塞方式 - 同步查询
function blockingQuery() {
console.log('开始查询')
// 阻塞操作
const result = connection.querySync('SELECT * FROM users')
console.log('查询完成')
return result
}
// 非阻塞方式 - 异步查询
function nonBlockingQuery(callback) {
console.log('开始查询')
// 非阻塞操作
connection.query('SELECT * FROM users', (err, result) => {
if (err) {
callback(err, null)
return
}
console.log('查询完成')
callback(null, result)
})
}
// 使用示例
nonBlockingQuery((err, result) => {
if (err) {
console.error('查询失败:', err)
return
}
console.log('查询结果:', result)
})
现代异步编程
Promise 方式
const fs = require('fs').promises
// 使用 Promise 的非阻塞 I/O
async function modernNonBlocking() {
console.log('开始读取文件')
try {
const data = await fs.readFile('large-file.txt', 'utf8')
console.log('文件读取完成,大小:', data.length)
} catch (error) {
console.error('读取文件失败:', error.message)
}
console.log('继续执行其他任务')
}
modernNonBlocking()
并行处理
const fs = require('fs').promises
// 并行处理多个文件
async function parallelProcessing() {
console.log('开始并行处理')
try {
const [data1, data2, data3] = await Promise.all([
fs.readFile('file1.txt', 'utf8'),
fs.readFile('file2.txt', 'utf8'),
fs.readFile('file3.txt', 'utf8'),
])
console.log('所有文件读取完成')
console.log('文件1大小:', data1.length)
console.log('文件2大小:', data2.length)
console.log('文件3大小:', data3.length)
} catch (error) {
console.error('处理失败:', error.message)
}
}
parallelProcessing()
总结
- 阻塞 I/O:同步执行,等待完成,简单但效率低
- 非阻塞 I/O:异步执行,立即返回,复杂但效率高
- Node.js 优势:非阻塞 I/O 实现高并发
- 现代开发:使用 Promise/async-await 简化异步编程
- 选择原则:I/O 密集型任务使用非阻塞,CPU 密集型任务需要特殊处理