后端基础概念 - 前端开发者指南
目录
- [进程 (Process)](#进程 (Process) "#%E8%BF%9B%E7%A8%8B-process")
- [线程 (Thread)](#线程 (Thread) "#%E7%BA%BF%E7%A8%8B-thread")
- [进程 vs 线程 对比](#进程 vs 线程 对比 "#%E8%BF%9B%E7%A8%8B-vs-%E7%BA%BF%E7%A8%8B-%E5%AF%B9%E6%AF%94")
- [单线程 vs 多线程 vs 多进程](#单线程 vs 多线程 vs 多进程 "#%E5%8D%95%E7%BA%BF%E7%A8%8B-vs-%E5%A4%9A%E7%BA%BF%E7%A8%8B-vs-%E5%A4%9A%E8%BF%9B%E7%A8%8B")
- [前端视角:JavaScript 的单线程模型](#前端视角:JavaScript 的单线程模型 "#%E5%89%8D%E7%AB%AF%E8%A7%86%E8%A7%92javascript-%E7%9A%84%E5%8D%95%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B")
- [事务 (Transaction)](#事务 (Transaction) "#%E4%BA%8B%E5%8A%A1-transaction")
- 什么时候需要加事务?
- 事务实战案例
- [常见问题 FAQ](#常见问题 FAQ "#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98-faq")
进程 (Process)
什么是进程?
进程 = 正在运行的程序
打个比方:
- 程序 = 食谱(静态的,躺在硬盘里)
- 进程 = 正在按食谱做饭(动态的,正在运行)
进程的特点
┌─────────────────────────────────────┐
│ 进程 │
├─────────────────────────────────────┤
│ 独立的内存空间(厨房) │
│ 独立的资源(锅碗瓢盆) │
│ 至少有一个线程(至少一个厨师) │
│ 进程间相互隔离(各做各的菜) │
└─────────────────────────────────────┘
前端类比
想象你打开了多个 Chrome 标签页:
- 每个标签页 = 一个进程
- 每个进程有自己的内存,互不干扰
- 一个标签页崩溃不会影响其他标签页
Node.js 中的进程
javascript
// 查看当前进程信息
console.log('进程 ID:', process.pid); // 进程唯一标识
console.log('进程标题:', process.title); // 进程名称
console.log('内存使用:', process.memoryUsage()); // 内存占用
// 退出进程
process.exit(0);
线程 (Thread)
什么是线程?
线程 = 进程中的执行单元
打个比方:
- 进程 = 餐厅(有厨房、厨师、食材)
- 线程 = 厨师(实际干活的)
一个进程可以有多个线程(一个餐厅可以有多个厨师)。
线程的特点
┌─────────────────────────────────────┐
│ 线程 │
├─────────────────────────────────────┤
│ 共享进程的内存空间(共用厨房) │
│ 共享进程的资源(共用锅碗瓢盆) │
│ 轻量级,创建开销小 │
│ 需要处理并发问题(避免抢锅铲) │
└─────────────────────────────────────┘
为什么需要多线程?
问题场景: 下载大文件时界面卡死
javascript
// 单线程:下载时界面卡死
function downloadBigFile() {
// 下载需要 10 秒
// 这 10 秒内,界面完全无法响应点击
const data = fetch('large-file.zip');
return data;
}
解决方案: 多线程
javascript
// 多线程:下载在后台线程进行,界面仍然可以响应
const worker = new Worker('download-worker.js');
worker.postMessage({ url: 'large-file.zip' });
worker.onmessage = (e) => {
console.log('下载完成', e.data);
};
// 主线程继续处理用户点击,不卡顿
进程 vs 线程 对比
| 特性 | 进程 | 线程 |
|---|---|---|
| 内存 | 独立内存空间 | 共享进程内存 |
| 通信 | 较复杂(需要 IPC) | 简单(直接读写共享变量) |
| 安全性 | 更安全(相互隔离) | 需要同步(可能冲突) |
| 开销 | 大(创建慢、占内存多) | 小(创建快、占内存少) |
| 崩溃影响 | 只影响自己 | 可能导致整个进程崩溃 |
| 比喻 | 餐厅 | 厨师 |
图解
css
┌─────────────────────────────────────────────────────────┐
│ 操作系统 │
├──────────────────┬──────────────────┬──────────────────┤
│ 进程 A │ 进程 B │ 进程 C │
│ ┌────────────┐ │ ┌────────────┐ │ ┌────────────┐ │
│ │ 内存空间 A │ │ │ 内存空间 B │ │ │ 内存空间 C │ │
│ ├────────────┤ │ ├────────────┤ │ ├────────────┤ │
│ │ 线程1 线程2│ │ │ 线程1 │ │ │ 线程1 线程2│ │
│ │ 线程3 │ │ │ 线程2 线程3│ │ │ │ │
│ └────────────┘ │ └────────────┘ │ └────────────┘ │
└──────────────────┴──────────────────┴──────────────────┘
进程间内存隔离,线程间共享内存
单线程 vs 多线程 vs 多进程
1. 单线程
定义: 一个进程只有一个线程,所有任务排队执行。
css
任务队列:[任务1] → [任务2] → [任务3] → [任务4]
执行完才执行下一个
优点:
- 简单,不用考虑并发问题
- 没有线程切换开销
缺点:
- 一个任务阻塞,整个程序卡死
- 无法利用多核 CPU
例子: Node.js 主线程
javascript
// Node.js 主线程是单线程的
console.log('开始');
setTimeout(() => {
console.log('定时器1');
}, 0);
// 这个循环会阻塞后面所有代码
while (true) {
// 死循环,定时器永远不会执行
break;
}
console.log('结束');
2. 多线程
定义: 一个进程有多个线程,并行执行多个任务。
css
主线程: [任务1] → [任务3]
线程2: [任务2] → [任务4]
线程3: [任务5] → [任务6]
三个线程同时执行
优点:
- 充分利用多核 CPU
- 一个线程阻塞不影响其他线程
缺点:
- 需要处理线程同步(复杂)
- 可能出现死锁、竞态条件
例子: Java、Python 多线程
java
// Java 多线程示例
Thread thread1 = new Thread(() -> {
System.out.println("线程1执行");
});
Thread thread2 = new Thread(() -> {
System.out.println("线程2执行");
});
thread1.start(); // 启动线程1
thread2.start(); // 启动线程2
// 两个线程同时执行
3. 多进程
定义: 启动多个进程,每个进程独立运行。
css
进程1:[服务实例1]
进程2:[服务实例2]
进程3:[服务实例3]
每个进程独立,互不干扰
优点:
- 进程隔离,一个崩溃不影响其他
- 稳定性高
缺点:
- 内存占用大
- 进程间通信复杂
例子: Node.js 集群、Nginx 多进程
javascript
// Node.js 集群模式
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
// 主进程:创建多个工作进程
const cpuCount = os.cpus().length; // CPU 核心数
for (let i = 0; i < cpuCount; i++) {
cluster.fork(); // 创建工作进程
}
} else {
// 工作进程:处理请求
require('./app');
}
对比表格
| 模型 | 并发能力 | 内存占用 | 稳定性 | 复杂度 | 适用场景 |
|---|---|---|---|---|---|
| 单线程 | 弱 | 低 | 低 | 低 | 简单任务、I/O密集型 |
| 多线程 | 强 | 中 | 中 | 高 | CPU密集型、计算任务 |
| 多进程 | 强 | 高 | 高 | 中 | 高可用服务、集群部署 |
前端视角:JavaScript 的单线程模型
JavaScript 为什么是单线程?
历史原因: JavaScript 最初是为了处理网页交互设计的,如果多线程,两个线程同时操作 DOM 会很混乱。
ini
线程1:document.body.innerHTML = 'A';
线程2:document.body.innerHTML = 'B';
// 结果是什么?A 还是 B?冲突!
单线程如何处理异步?
JavaScript 使用 事件循环 (Event Loop):
scss
┌─────────────────────────────────────────┐
│ 事件循环 (Event Loop) │
├─────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────────┐ │
│ │ 调用栈 │ ←─── │ 任务队列 │ │
│ │ (Stack) │ │ (Task Queue) │ │
│ └─────────┘ └─────────────┘ │
│ ↓ 执行 ↑ 等待 │
│ 同步代码 异步回调 │
│ │
└─────────────────────────────────────────┘
例子:
javascript
console.log('1. 开始'); // 同步,立即执行
setTimeout(() => {
console.log('2. 定时器'); // 异步,放入任务队列
}, 0);
console.log('3. 结束'); // 同步,立即执行
// 输出顺序:1 → 3 → 2
// 因为 setTimeout 是异步的,等同步代码执行完才执行
Web Worker:JavaScript 的"多线程"
虽然主线程是单线程的,但可以使用 Web Worker 创建后台线程:
javascript
// 主线程
const worker = new Worker('worker.js');
worker.postMessage({ data: '发送给 Worker' });
worker.onmessage = (e) => {
console.log('Worker 返回:', e.data);
};
// worker.js(后台线程)
self.onmessage = (e) => {
// 执行耗时计算,不阻塞主线程
const result = heavyComputation(e.data);
self.postMessage(result);
};
事务 (Transaction)
什么是事务?
事务 = 一组操作,要么全部成功,要么全部失败
打个比方:
- 转账 = 一个事务
- A 转 B 100 元,包含两个操作:
- A 账户 -100 元
- B 账户 +100 元
- 如果操作1成功,操作2失败 → 整个事务失败,A 的钱不能少
事务的 ACID 特性
css
┌─────────────────────────────────────────────────────────┐
│ ACID 特性 │
├─────────────────────────────────────────────────────────┤
│ │
│ A - Atomicity(原子性) │
│ 要么全部成功,要么全部失败 │
│ 例:转账要么成功,要么回到原状态 │
│ │
│ C - Consistency(一致性) │
│ 事务前后数据一致,满足约束条件 │
│ 例:转账前后总金额不变 │
│ │
│ I - Isolation(隔离性) │
│ 多个事务互不干扰 │
│ 例:A转账时,B不能同时转给A │
│ │
│ D - Durability(持久性) │
│ 事务提交后永久保存 │
│ 例:转账成功后,数据库永久记录 │
│ │
└─────────────────────────────────────────────────────────┘
前端类比
前端开发中类似事务的场景:
javascript
// 表单提交:要么全部成功,要么全部回滚
async function submitForm(formData) {
try {
// 1. 验证表单
validateForm(formData);
// 2. 提交数据
await saveToDatabase(formData);
// 3. 更新 UI
updateUI();
// 全部成功
} catch (error) {
// 任何一步失败,回滚 UI
revertUI();
showError(error);
}
}
什么时候需要加事务?
判断标准
问自己一个问题:这组操作是否需要"要么全成功,要么全失败"?
markdown
需要事务?
│
├─ 涉及多个数据库操作? ─── 否 ──→ 不需要事务
│
│ 是
│
├─ 操作之间有依赖关系? ─── 否 ──→ 可能不需要
│
│ 是
│
├─ 部分失败会导致数据不一致? ─── 否 ──→ 可能不需要
│
│ 是
│
└─ 需要事务!
常见需要事务的场景
| 场景 | 原因 |
|---|---|
| 转账 | A扣钱+B加钱必须同时成功 |
| 订单创建 | 订单+库存+支付记录必须一致 |
| 用户注册 | 用户信息+默认设置+初始化数据 |
| 批量导入 | 导入的数据要么全进,要么全不进 |
| 库存扣减 | 订单创建+库存减少必须同步 |
常见不需要事务的场景
| 场景 | 原因 |
|---|---|
| 单表单条插入 | 只有一个操作,失败就失败了 |
| 查询操作 | 只读,不需要事务 |
| 日志记录 | 即使失败也不影响业务 |
| 缓存更新 | 可以容忍短暂不一致 |
事务实战案例
案例 1:转账(经典事务)
javascript
// Node.js + MySQL 事务示例
async function transferMoney(fromId, toId, amount) {
const connection = await db.getConnection();
try {
// 1. 开启事务
await connection.beginTransaction();
// 2. A 账户扣钱
await connection.query(
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
[amount, fromId]
);
// 3. B 账户加钱
await connection.query(
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
[amount, toId]
);
// 4. 提交事务
await connection.commit();
console.log('转账成功');
} catch (error) {
// 5. 回滚事务
await connection.rollback();
console.log('转账失败,已回滚');
throw error;
} finally {
connection.release();
}
}
如果不加事务会怎样?
css
场景:A 转 B 100元
无事务:
1. A 账户 -100 元 ✓
2. 此时数据库崩溃
3. B 账户 +100 元 ✗(没执行)
结果:A 少了 100,B 没收到,钱凭空消失!
有事务:
1. A 账户 -100 元 ✓
2. 此时数据库崩溃
3. 事务自动回滚,A 的钱回来
结果:A 和 B 的钱都没变,安全!
案例 2:创建订单
javascript
// 创建订单事务
async function createOrder(userId, products) {
const connection = await db.getConnection();
try {
await connection.beginTransaction();
// 1. 创建订单
const [orderResult] = await connection.query(
'INSERT INTO orders (user_id, total, status) VALUES (?, ?, ?)',
[userId, calculateTotal(products), 'pending']
);
const orderId = orderResult.insertId;
// 2. 创建订单项
for (const product of products) {
await connection.query(
'INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)',
[orderId, product.id, product.quantity, product.price]
);
}
// 3. 扣减库存
for (const product of products) {
await connection.query(
'UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?',
[product.quantity, product.id, product.quantity]
);
}
// 4. 清空购物车
await connection.query(
'DELETE FROM cart_items WHERE user_id = ?',
[userId]
);
await connection.commit();
return orderId;
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
}
案例 3:用户注册
javascript
// 用户注册事务
async function registerUser(userData) {
const connection = await db.getConnection();
try {
await connection.beginTransaction();
// 1. 创建用户
const [userResult] = await connection.query(
'INSERT INTO users (username, email, password) VALUES (?, ?, ?)',
[userData.username, userData.email, userData.password]
);
const userId = userResult.insertId;
// 2. 创建用户配置
await connection.query(
'INSERT INTO user_settings (user_id, theme, language) VALUES (?, ?, ?)',
[userId, 'light', 'zh-CN']
);
// 3. 创建用户钱包
await connection.query(
'INSERT INTO wallets (user_id, balance) VALUES (?, ?)',
[userId, 0]
);
// 4. 发送欢迎消息
await connection.query(
'INSERT INTO messages (user_id, content) VALUES (?, ?)',
[userId, '欢迎加入!']
);
await connection.commit();
return userId;
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
}
案例 4:不需要事务的场景
javascript
// 记录访问日志 - 不需要事务
async function logVisit(userId, page) {
// 只有一个插入操作,失败了也没关系
await db.query(
'INSERT INTO visit_logs (user_id, page, time) VALUES (?, ?, NOW())',
[userId, page]
);
}
// 更新用户头像 - 不需要事务
async function updateAvatar(userId, avatarUrl) {
// 只有一个更新操作
await db.query(
'UPDATE users SET avatar = ? WHERE id = ?',
[avatarUrl, userId]
);
}
常见问题 FAQ
Q1: 进程和线程的区别用一句话概括?
进程是资源分配的单位,线程是执行调度的单位。
进程 = 厨房(空间、设备) 线程 = 厨师(干活的人)
Q2: 为什么 Node.js 是单线程但能处理高并发?
Node.js 单线程是指主线程,但它利用:
- 事件循环:高效处理 I/O
- 异步非阻塞:一个 I/O 等待时,处理其他请求
- Worker 线程:CPU 密集型任务可以交给 Worker
erlang
传统多线程服务器:
请求1 → 创建线程1 → 等待数据库 → 线程占用内存
请求2 → 创建线程2 → 等待数据库 → 线程占用内存
...
内存消耗大
Node.js 单线程:
请求1 → 注册回调 → 处理其他请求
请求2 → 注册回调 → 处理其他请求
...
数据库返回 → 执行回调
内存消耗小
Q3: 什么时候用多进程,什么时候用多线程?
| 场景 | 选择 | 原因 |
|---|---|---|
| 需要高稳定性 | 多进程 | 进程隔离,一个崩溃不影响其他 |
| 需要高性能计算 | 多线程 | 线程切换开销小,共享内存快 |
| 需要简单可靠 | 多进程 | 不用处理复杂的线程同步 |
| 需要共享大量数据 | 多线程 | 共享内存,避免进程间通信开销 |
Q4: 事务会锁住整个数据库吗?
不会。 事务只锁住涉及的行或表:
css
事务1:更新用户 A 的余额 → 锁住用户 A 的行
事务2:更新用户 B 的余额 → 锁住用户 B 的行
两者互不影响,可以并发执行
事务1:更新所有用户的余额 → 锁住整张表
此时其他事务必须等待
Q5: 事务失败了需要手动回滚吗?
是的,在代码中需要处理:
javascript
try {
await connection.beginTransaction();
// 执行操作
await connection.commit(); // 成功则提交
} catch (error) {
await connection.rollback(); // 失败则回滚
throw error;
}
Q6: 前端需要关心事务吗?
通常不需要。 事务是后端和数据库层面的概念。
但了解事务有助于:
- 理解 API 的错误处理
- 设计更好的提交逻辑
- 与后端沟通数据一致性问题
Q7: 分布式事务是什么?
跨多个服务/数据库的事务。
单体应用事务:
订单服务 + 库存服务 + 支付服务 → 同一个数据库 → 本地事务
分布式事务:
订单服务 → 订单数据库
库存服务 → 库存数据库 → 分布式事务
支付服务 → 支付数据库
难点:三个数据库不在同一台机器,如何保证一致性?
解决方案:Saga、TCC、Seata 等分布式事务框架
总结速查表
| 概念 | 定义 | 一句话理解 |
|---|---|---|
| 进程 | 运行中的程序 | 有独立内存的应用实例 |
| 线程 | 进程中的执行单元 | 共享内存的执行者 |
| 单线程 | 一个进程一个线程 | 排队执行,简单但可能阻塞 |
| 多线程 | 一个进程多个线程 | 并行执行,需处理同步 |
| 多进程 | 多个进程运行 | 稳定隔离,内存占用大 |
| 事务 | 一组操作的原子单位 | 要么全成功,要么全失败 |
| 场景 | 需要事务? | 原因 |
|---|---|---|
| 转账 | ✅ 需要 | A扣钱+B加钱必须同时成功 |
| 创建订单 | ✅ 需要 | 订单+库存必须一致 |
| 用户注册 | ✅ 需要 | 用户信息+初始化数据 |
| 单表插入 | ❌ 不需要 | 只有一个操作 |
| 查询数据 | ❌ 不需要 | 只读操作 |
| 记录日志 | ❌ 不需要 | 失败不影响业务 |
延伸阅读
文档版本: v1.0 更新时间: 2026-07-10 适用人群: 前端开发者、后端初学者