后端基础概念 - 前端开发者指南(进程、线程、事务)

后端基础概念 - 前端开发者指南


目录

  1. [进程 (Process)](#进程 (Process) "#%E8%BF%9B%E7%A8%8B-process")
  2. [线程 (Thread)](#线程 (Thread) "#%E7%BA%BF%E7%A8%8B-thread")
  3. [进程 vs 线程 对比](#进程 vs 线程 对比 "#%E8%BF%9B%E7%A8%8B-vs-%E7%BA%BF%E7%A8%8B-%E5%AF%B9%E6%AF%94")
  4. [单线程 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")
  5. [前端视角: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")
  6. [事务 (Transaction)](#事务 (Transaction) "#%E4%BA%8B%E5%8A%A1-transaction")
  7. 什么时候需要加事务?
  8. 事务实战案例
  9. [常见问题 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 元,包含两个操作:
    1. A 账户 -100 元
    2. 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 适用人群: 前端开发者、后端初学者

相关推荐
倔强的石头_1 小时前
从5万次到日均2000万次调用:大模型推理服务的性能工程实践V2.1
后端
环境栈笔记2 小时前
多账号浏览器选型检查清单:Profile、权限、Session 和任务日志怎么评估
前端·人工智能·后端·自动化
shepherd1112 小时前
一次把 Spring MVC 文件上传参数“查没了”的排查:multipart、Filter 与 request body 的连环坑
java·spring boot·后端
mONESY2 小时前
Node.js fs 文件系统模块 四种读写方案全解析
javascript·后端
Larcher2 小时前
从回调地狱到优雅异步:Node.js 文件操作进化史
javascript·后端·架构
Java编程爱好者2 小时前
深入浅出Agent: Harness最全调研与原理解析(持续更新中)
后端
日月云棠2 小时前
Spring 源码深度剖析:DispatcherServlet 的自动化注册机制
java·后端
码事漫谈3 小时前
让 Codex CLI 跑上 DeepSeek:用 LiteLLM 做 Responses↔Chat 协议翻译
后端
JavaGuide3 小时前
又一个画图 Skill 开源,再见手动画 draw.io!
前端·后端·github