还在被同步、异步、阻塞、非阻塞、进程、线程、协程、并发、并行这些概念绕晕吗?看完这篇,或许你会发现它们并没有想象中那么复杂。
许多后端开发者在面试或学习时,常常被问到这些看似相似却又不同的概念:
- “同步和异步有什么区别?”
- “阻塞和非阻塞是一回事吗?”
- “进程、线程、协程到底啥关系?”
- “并发和并行能一样吗?”
听起来熟悉?别慌!今天我们用最通俗的方式,配合图解和代码,系统性地厘清这些让人头疼的核心概念。
一、核心概念速览
先上一张全局关系图,帮助你建立整体认知:
┌─────────────────────────────────────────────────┐
│ 应用程序 (Application) │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ 进程 (Process) │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 线程 Thread │ │ 线程 Thread │ │ │
│ │ │ ┌───────┐ │ │ ┌───────┐ │ │ │
│ │ │ │协程 │ │ │ │协程 │ │ │ │
│ │ │ │Cortn │ │ │ │Cortn │ │ │ │
│ │ │ └───────┘ │ │ └───────┘ │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ └───────────────────────────────────────┘ │
│ │
│ 执行方式: 同步 vs 异步 │
│ 等待策略: 阻塞 vs 非阻塞 │
│ 任务关系: 并发 vs 并行 │
└─────────────────────────────────────────────────┘
记住一句话:这些概念是分层次、分维度的,千万别混为一谈!
二、同步 vs 异步:关注的是“执行顺序”
核心区别
同步(Synchronous):
- 任务按顺序执行
- 必须等当前任务完成,才能执行下一个
- 调用者需要等待结果返回
异步(Asynchronous):
- 任务可以独立执行
- 不需要等待当前任务完成
- 调用者发起操作后,立即返回,结果通过回调/Future等方式通知
生活类比
同步就像你去餐厅点餐:
你点餐 → 等厨师做饭 → 拿到饭 → 开始吃
(你一直在等,啥也干不了)
异步就像你点外卖:
你下单 → 立即去做其他事 → 外卖到了通知你 → 去拿外卖
(你下单后不用傻等,可以继续工作)
C++代码示例
同步读取文件:
#include<iostream>
#include<fstream>
#include<string>
void synchronous_read() {
std::cout << "开始读取文件..." << std::endl;
std::ifstream file("data.txt");
std::string content;
// 这里会阻塞,直到读取完成
if (file.is_open()) {
std::getline(file, content, '\0');
file.close();
}
std::cout << "文件读取完成: " << content << std::endl;
std::cout << "继续执行其他操作" << std::endl;
}
异步读取文件(使用C++20协程):
#include<iostream>
#include<coroutine>
#include<thread>
#include<future>
// 简化的异步文件读取
std::future<std::string> async_read_file(const std::string& filename) {
return std::async(std::launch::async, [filename]() {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟IO
return "文件内容: Hello World";
});
}
void asynchronous_read() {
std::cout << "开始异步读取文件..." << std::endl;
// 发起异步操作,立即返回
auto future = async_read_file("data.txt");
std::cout << "可以继续做其他事情!" << std::endl;
std::cout << "做一些其他工作..." << std::endl;
// 需要结果时再获取
std::string content = future.get();
std::cout << "异步读取完成: " << content << std::endl;
}
三、阻塞 vs 非阻塞:关注的是“等待状态”
核心区别
阻塞(Blocking):
- 调用时,线程会挂起等待
- 线程失去CPU控制权,不能做其他事
- 直到操作完成或超时才返回
非阻塞(Non-blocking):
- 调用时,立即返回结果(可能是“还没好”的状态)
- 线程保持活跃,可以去做其他事
- 需要轮询或通过事件通知获取最终结果
生活类比
阻塞就像你去银行窗口办业务:
你排队 → 叫到号 → 站在窗口前 → 一直等业务员办完
(这期间你啥也干不了,必须站在那)
非阻塞就像你取号后:
你取号 → 去旁边玩手机 → 时不时看看叫号屏幕 → 叫到号再去
(你可以做其他事,不用傻站着)
C++代码示例
阻塞IO:
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
void blocking_read() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
// ... 连接服务器代码省略 ...
char buffer[1024];
std::cout << "等待接收数据(阻塞中)..." << std::endl;
// recv是阻塞调用,会一直等待直到有数据到达
ssize_t bytes = recv(sock, buffer, sizeof(buffer), 0);
std::cout << "收到数据: " << bytes << " 字节" << std::endl;
// 只有收到数据后,才会执行到这里
close(sock);
}
非阻塞IO:
#include<iostream>
#include<fcntl.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<errno.h>
void non_blocking_read() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
// 设置为非阻塞模式
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
// ... 连接服务器代码省略 ...
char buffer[1024];
while (true) {
// recv立即返回,不会等待
ssize_t bytes = recv(sock, buffer, sizeof(buffer), 0);
if (bytes > 0) {
std::cout << "收到数据: " << bytes << " 字节" << std::endl;
break;
} else if (bytes == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
// 暂时没有数据,可以做其他事
std::cout << "暂无数据,做点其他事..." << std::endl;
sleep(1); // 模拟做其他工作
} else {
// 真正的错误
break;
}
}
close(sock);
}
四、重要区分:同步/异步 与 阻塞/非阻塞
很多人把这两对概念混淆,其实它们是两个不同维度!
┌─────────────────────────────────────────┐
│ 同步/异步: 关注任务执行顺序(消息通知机制)|
│ 阻塞/非阻塞: 关注线程等待状态 │
└─────────────────────────────────────────┘
可以组合出5种主要场景:
| 组合 |
特点 |
使用场景 |
例子 |
| 同步阻塞 |
发起调用后阻塞等待完成 |
简单场景,逻辑清晰 |
普通的read/write |
| 同步非阻塞(轮询) |
立即返回,需主动重试 |
需要持续检查状态 |
O_NONBLOCK + 轮询 |
| 同步非阻塞(多路复用) |
阻塞等待多个FD就绪 |
高并发服务器 |
select/poll/epoll |
| 异步阻塞 |
发起异步操作但阻塞等待 |
罕见,效率低 |
aio_read + aio_suspend |
| 异步非阻塞 |
发起后立即返回,完成后回调 |
极致性能 |
io_uring, Linux AIO, IOCP |
⚠️ 重点澄清:select/poll/epoll的真实身份
很多人误以为epoll是“异步非阻塞”,这是最常见的误解!
权威定义(来自POSIX标准):
同步I/O:导致请求进程阻塞,直到I/O操作完成
异步I/O:不导致请求进程阻塞
根据这个定义,select/poll/epoll都属于同步I/O,因为最终的数据拷贝操作(recvfrom)仍然会阻塞进程。
select/poll/epoll的本质:
I/O多路复用(同步) = 用一个阻塞调用同时监听多个文件描述符
为什么容易混淆?
- epoll_wait()本身确实会阻塞,等待事件发生
- 但监听的socket通常设为非阻塞模式
- 它能单线程高并发(看起来像异步),但本质是同步的快速切换
真正的异步非阻塞IO(数据拷贝也不阻塞):
- Linux:
io_uring(最新)、POSIX AIO(aio_read/aio_write)
- Windows:
IOCP(I/O Completion Ports)
- 特点:整个I/O操作完成后才通知应用,期间进程完全不阻塞
实际应用场景:
- 简单工具:同步阻塞(够用,代码简单)
- 高并发服务器:I/O多路复用(epoll模型,C10K问题的经典解法)
- 超高性能场景:异步非阻塞(io_uring,适合存储系统)
C++示例对比
1. 同步阻塞(最简单):
// 普通的阻塞读取
int fd = open("file.txt", O_RDONLY);
char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf)); // 阻塞直到读取完成
2. 同步非阻塞+轮询(低效):
// 设置非阻塞
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
// 轮询读取
while (true) {
ssize_t n = read(fd, buf, sizeof(buf));
if (n > 0) break; // 读到数据
if (errno == EAGAIN) {
// 暂时无数据,继续轮询(浪费CPU)
continue;
}
}
3. I/O多路复用epoll(高并发首选):
#include<iostream>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<fcntl.h>
#include<vector>
#define MAX_EVENTS 10
// 同步IO多路复用示例:使用epoll处理多个连接
void epoll_example() {
// 1. 创建epoll实例
int epoll_fd = epoll_create1(0);
// 2. 创建监听socket
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
// 设置为非阻塞(但epoll_wait本身仍是阻塞的)
int flags = fcntl(listen_fd, F_GETFL, 0);
fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK);
// 绑定和监听...
// bind(listen_fd, ...);
// listen(listen_fd, ...);
// 3. 将监听socket加入epoll
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = listen_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);
struct epoll_event events[MAX_EVENTS];
// 4. 事件循环
while (true) {
// epoll_wait是阻塞的,等待事件发生
// 这就是为什么它是“同步”的
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
std::cout << "epoll_wait返回,有 " << nfds << " 个事件就绪" << std::endl;
// 5. 处理就绪的事件
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == listen_fd) {
// 新连接到达
int conn_fd = accept(listen_fd, nullptr, nullptr);
// 将新连接加入epoll监听
ev.events = EPOLLIN | EPOLLET; // 边缘触发
ev.data.fd = conn_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev);
std::cout << "新连接: " << conn_fd << std::endl;
} else {
// 已有连接有数据可读
char buf[1024];
int n = read(events[i].data.fd, buf, sizeof(buf));
if (n > 0) {
std::cout << "读取 " << n << " 字节" << std::endl;
// 处理数据...
} else {
// 连接关闭
close(events[i].data.fd);
}
}
}
}
close(epoll_fd);
}
关键点:
epoll_wait()调用是阻塞的,会等待事件发生
- 虽然可以监听多个非阻塞socket,但epoll本身是同步的
- 这种模式叫“同步IO多路复用”,不是“异步IO”
真正的异步非阻塞:io_uring示例
#include<iostream>
#include<liburing.h>
// 真正的异步非阻塞IO:io_uring
void async_io_uring_example() {
struct io_uring ring;
io_uring_queue_init(32, &ring, 0);
char buffer[1024];
int fd = open("test.txt", O_RDONLY);
// 1. 提交异步读请求(立即返回,不阻塞)
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buffer, sizeof(buffer), 0);
io_uring_sqe_set_data(sqe, buffer);
io_uring_submit(&ring);
std::cout << "异步读请求已提交,立即返回,可以做其他事..." << std::endl;
std::cout << "做其他工作..." << std::endl;
// 2. 等待完成(这里才阻塞)
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
// 3. 处理结果
if (cqe->res >= 0) {
std::cout << "异步读取完成: " << cqe->res << " 字节" << std::endl;
}
io_uring_cqe_seen(&ring, cqe);
io_uring_queue_exit(&ring);
close(fd);
}
关键点:
- 整个read操作在后台完成,进程完全不阻塞(除非主动wait)
- 这才是真正的“异步非阻塞IO”
- 适用于极致性能场景(如数据库、存储系统)
对比:
- epoll:调用epoll_wait时阻塞,等待FD就绪,然后你自己read(同步)
- io_uring:提交读请求后立即返回,内核完成读取后通知你(异步)
异步阻塞的真实例子(罕见但存在)
#include<aio.h>
#include<fcntl.h>
#include<string.h>
#include<iostream>
// 异步阻塞:发起异步操作,但阻塞等待结果(低效,罕见)
void async_blocking_example() {
int fd = open("file.txt", O_RDONLY);
char buffer[1024];
// 设置AIO控制块
struct aiocb cb;
memset(&cb, 0, sizeof(cb));
cb.aio_fildes = fd;
cb.aio_buf = buffer;
cb.aio_nbytes = sizeof(buffer);
cb.aio_offset = 0;
// 1. 发起异步读取(立即返回,这是“异步”)
std::cout << "发起异步读取..." << std::endl;
aio_read(&cb);
std::cout << "理论上可以做其他事,但我们选择等待..." << std::endl;
// 2. 主动阻塞等待异步操作完成(这是“阻塞”)
// 这就是“异步阻塞”的典型场景
const struct aiocb* cblist[] = {&cb};
aio_suspend(cblist, 1, nullptr); // ← 阻塞在这里
std::cout << "读取完成: " << aio_return(&cb) << " 字节" << std::endl;
close(fd);
}
为什么说异步阻塞低效且罕见?
- 矛盾:发起异步操作是为了不阻塞,但最后又阻塞等待
- 效率低:既然要阻塞等待结果,不如直接用同步I/O,代码更简单
- 唯一好处:可以同时发起多个异步操作,然后统一等待(批量处理)
真实应用场景(少见):
// 同时读取多个文件,然后一起等待
aio_read(&cb1); // 文件1
aio_read(&cb2); // 文件2
aio_read(&cb3); // 文件3
// 现在三个读取都在后台并行进行
aio_suspend(all_cbs, 3, nullptr); // 阻塞等待全部完成
五、进程、线程、协程:资源粒度递减
核心区别
进程(Process)
├─ 最重量级
├─ 独立内存空间
├─ 创建开销大(毫秒级)
└─ 隔离性强,崩溃不影响其他进程
线程(Thread)
├─ 中等重量
├─ 共享进程内存
├─ 创建开销中等(微秒级)
└─ 一个线程崩溃可能导致整个进程崩溃
协程(Coroutine)
├─ 最轻量级
├─ 在线程内调度
├─ 创建开销小(纳秒级)
└─ 协作式调度,需要主动让出CPU
性能对比
| 特性 |
进程 |
线程 |
协程 |
| 创建时间 |
毫秒(ms) |
微秒(μs) |
纳秒(ns) |
| 内存占用 |
数MB |
~1MB栈 |
几十字节 |
| 切换开销 |
大 |
中 |
极小 |
| 通信方式 |
IPC(管道/共享内存) |
直接共享内存 |
直接访问 |
| 并行能力 |
真并行 |
真并行 |
并发(非并行) |
C++代码对比
进程示例:
#include<iostream>
#include<unistd.h>
#include<sys/wait.h>
void process_example() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
std::cout << "子进程ID: " << getpid() << std::endl;
sleep(2);
exit(0);
} else {
// 父进程
std::cout << "父进程ID: " << getpid()
<< ", 子进程ID: " << pid << std::endl;
wait(nullptr); // 等待子进程结束
}
}
线程示例:
#include<iostream>
#include<thread>
#include<vector>
void worker(int id) {
std::cout << "线程 " << id << " 开始工作" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "线程 " << id << " 完成工作" << std::endl;
}
void thread_example() {
std::vector<std::thread> threads;
// 创建5个线程
for (int i = 0; i < 5; i++) {
threads.emplace_back(worker, i);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
}
协程示例(C++20):
#include<iostream>
#include<coroutine>
#include<thread>
// 简单的协程返回类型
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
Task coroutine_worker(int id) {
std::cout << "协程 " << id << " 开始工作" << std::endl;
// 模拟异步操作
co_await std::suspend_always{};
std::cout << "协程 " << id << " 恢复工作" << std::endl;
}
void coroutine_example() {
// 创建多个协程(非常轻量)
for (int i = 0; i < 100000; i++) {
coroutine_worker(i);
}
// 轻松创建10万个协程,换成线程内存早爆了!
}
六、并发 vs 并行:一个关于“时间”的故事
核心区别
并发(Concurrency):
- 多个任务交替执行
- 看起来同时进行,实际是快速切换
- 一个CPU也能实现并发
并行(Parallelism):
- 多个任务同时执行
- 需要多个CPU核心
- 真正的同时进行
终极类比
并发就像一个厨师做多道菜:
炒菜1 ──┐
├──> 厨师快速切换(单核CPU)
炒菜2 ──┘
结果:看起来同时在做,实际是快速切换
并行就像多个厨师同时做菜:
厨师1 ──> 炒菜1 (核心1)
厨师2 ──> 炒菜2 (核心2)
结果:真的同时在做
图解
并发 (Concurrency):
时间 ─────────────────────────>
[任务A][任务B][任务A][任务B]
单个CPU快速切换
并行 (Parallelism):
时间 ─────────────────────────>
CPU1 [任务A][任务A][任务A][任务A]
CPU2 [任务B][任务B][任务B][任务B]
多个CPU同时执行
C++代码示例
并发示例(单线程事件循环):
#include<iostream>
#include<queue>
#include<functional>
// 简单的事件循环(并发但不并行)
class EventLoop {
private:
std::queue<std::function<void()>> tasks;
public:
void add_task(std::function<void()> task) {
tasks.push(task);
}
void run() {
while (!tasks.empty()) {
auto task = tasks.front();
tasks.pop();
// 执行任务(单线程,交替执行)
task();
}
}
};
void concurrent_example() {
EventLoop loop;
// 添加多个任务
loop.add_task([]() {
std::cout << "任务1执行" << std::endl;
});
loop.add_task([]() {
std::cout << "任务2执行" << std::endl;
});
loop.add_task([]() {
std::cout << "任务3执行" << std::endl;
});
// 并发执行(快速切换)
loop.run();
}
并行示例(多线程):
#include<iostream>
#include<thread>
#include<vector>
void parallel_worker(int id) {
std::cout << "并行任务 " << id
<< " 在线程 " << std::this_thread::get_id()
<< " 执行" << std::endl;
}
void parallel_example() {
std::vector<std::thread> threads;
// 创建多个线程,真正并行执行
for (int i = 0; i < 4; i++) {
threads.emplace_back(parallel_worker, i);
}
for (auto& t : threads) {
t.join();
}
}
七、回调(Callback):异步编程的核心
什么是回调?
回调是异步操作完成后,用于通知调用者的函数。
生活类比
回调就像你点外卖时留的电话:
你下单 → 留电话(回调函数) → 去做其他事
外卖到了 → 外卖员打你电话(调用回调) → 你去取外卖
C++代码示例
#include<iostream>
#include<functional>
#include<thread>
#include<chrono>
// 异步下载函数,接受回调
void async_download(
const std::string& url,
std::function<void(const std::string&)> callback
) {
// 创建线程模拟异步下载
std::thread([url, callback]() {
std::cout << "开始下载: " << url << std::endl;
// 模拟下载耗时
std::this_thread::sleep_for(std::chrono::seconds(2));
// 下载完成,调用回调
callback("下载完成的数据");
}).detach();
}
void callback_example() {
std::cout << "主线程:发起下载请求" << std::endl;
// 发起异步下载,传入回调函数
async_download("http://example.com/file",
[](const std::string& data) {
std::cout << "回调执行: " << data << std::endl;
}
);
std::cout << "主线程:继续做其他事情" << std::endl;
// 等待异步操作完成
std::this_thread::sleep_for(std::chrono::seconds(3));
}
八、事件循环(Event Loop):异步的心脏
什么是事件循环?
事件循环是一个不断检查并处理事件的无限循环,是Node.js、浏览器等单线程异步系统的核心。
工作原理
┌───────────────────────────┐
│ 事件循环 (Event Loop) │
│ │
│ while(true) { │
│ 事件 = 获取下一个事件 │
│ if(事件) { │
│ 执行事件处理函数 │
│ } │
│ } │
└───────────────────────────┘
↑ │
│ ↓
┌─────────────────┐
│ 事件队列 │
│ [事件1] │
│ [事件2] │
│ [事件3] │
└─────────────────┘
C++实现简单事件循环
#include<iostream>
#include<queue>
#include<functional>
#include<thread>
#include<mutex>
#include<condition_variable>
class SimpleEventLoop {
private:
std::queue<std::function<void()>> event_queue;
std::mutex mtx;
std::condition_variable cv;
bool running = true;
public:
// 添加事件到队列
void post_event(std::function<void()> event) {
std::lock_guard<std::mutex> lock(mtx);
event_queue.push(event);
cv.notify_one();
}
// 事件循环主函数
void run() {
while (running) {
std::unique_lock<std::mutex> lock(mtx);
// 等待事件
cv.wait(lock, [this]() {
return !event_queue.empty() || !running;
});
if (!running) break;
// 取出事件
auto event = event_queue.front();
event_queue.pop();
lock.unlock();
// 执行事件处理
event();
}
}
void stop() {
running = false;
cv.notify_all();
}
};
void event_loop_example() {
SimpleEventLoop loop;
// 在另一个线程运行事件循环
std::thread loop_thread([&loop]() {
loop.run();
});
// 向事件循环投递事件
loop.post_event([]() {
std::cout << "事件1: 处理网络请求" << std::endl;
});
loop.post_event([]() {
std::cout << "事件2: 处理文件IO" << std::endl;
});
loop.post_event([]() {
std::cout << "事件3: 更新UI" << std::endl;
});
// 等待事件处理
std::this_thread::sleep_for(std::chrono::seconds(2));
loop.stop();
loop_thread.join();
}
九、实战总结:如何选择?
选择指南
| 场景 |
推荐方案 |
理由 |
| CPU密集型任务 |
多进程/多线程 |
需要真正的并行计算 |
| IO密集型任务(高并发) |
IO多路复用(epoll)+协程 |
单线程处理大量连接 |
| IO密集型任务(极致性能) |
io_uring(真异步) |
零拷贝,内核异步处理 |
| 简单脚本工具 |
同步阻塞 |
代码简单,够用就好 |
| 高性能服务器 |
事件循环+IO多路复用+线程池 |
结合多种技术 |
| Web后端 |
协程+IO多路复用 |
Go、Python asyncio方案 |
💡 最佳实践
- 默认用同步阻塞,除非有性能瓶颈
- IO密集用IO多路复用,CPU密集用多线程/进程
- 协程适合高并发场景(百万连接)
- 事件循环+epoll是主流高性能方案(Nginx、Redis、Node.js)
- 真正需要异步时用io_uring(Linux 5.1+)
技术栈对照表
| 技术/框架 |
使用的模型 |
| Nginx |
IO多路复用(epoll) + 事件循环 |
| Redis |
IO多路复用(epoll) + 单线程事件循环 |
| Node.js |
IO多路复用(libuv封装epoll) + 事件循环 |
| Go语言 |
协程(goroutine) + IO多路复用 |
| Rust Tokio |
协程(async/await) + IO多路复用/io_uring |
十、常见面试题秒答
Q1: 同步一定是阻塞的吗?
不一定!同步非阻塞就是反例(轮询),IO多路复用也是同步但可以高效处理大量连接。
Q2: epoll是异步IO吗?
不是!epoll是同步IO多路复用。epoll_wait()会阻塞等待事件,真正的异步IO是io_uring、Linux AIO。
Q3: 协程和线程的区别?
协程是用户态调度,轻量级;线程是内核态调度,重量级。
Q4: 单核CPU能实现并行吗?
不能!只能实现并发(快速切换),并行需要多核。
Q5: Node.js是单线程为什么能高并发?
单线程事件循环+IO多路复用(epoll)+异步非阻塞编程模型,通过事件驱动实现高并发。
十一、知识点速记卡
┌──────────────────────────────────────┐
│ 维度1: 消息通知机制 │
│ • 同步: 调用后等待操作完成 │
│ • 异步: 发起操作立即返回,完成后通知 │
├──────────────────────────────────────┤
│ 维度2: 等待状态 │
│ • 阻塞: 线程挂起等待 │
│ • 非阻塞: 立即返回,可做其他事 │
├──────────────────────────────────────┤
│ 常见IO模型 │
│ • 同步阻塞: read/write │
│ • 同步非阻塞: O_NONBLOCK轮询 │
│ • IO多路复用: select/poll/epoll(同步) │
│ • 异步非阻塞: io_uring/AIO/IOCP │
├──────────────────────────────────────┤
│ 维度3: 资源粒度 │
│ • 进程: 最重,独立内存,毫秒级 │
│ • 线程: 中等,共享内存,微秒级 │
│ • 协程: 最轻,用户态,纳秒级 │
├──────────────────────────────────────┤
│ 维度4: 任务关系 │
│ • 并发: 交替执行(单核可实现) │
│ • 并行: 同时执行(需多核) │
├──────────────────────────────────────┤
│ 异步机制 │
│ • 回调: 完成时调用函数 │
│ • 事件循环: 不断检查和处理事件 │
└──────────────────────────────────────┘
结语
这些概念看起来复杂,其实只要抓住核心维度,就能轻松理解:
- 同步/异步关注执行顺序
- 阻塞/非阻塞关注等待状态
- 进程/线程/协程是资源粒度
- 并发/并行是任务关系
- 回调/事件循环是实现机制
记住这个框架,以后再也不会混淆了!希望这篇深度解析能帮助你构建清晰的知识体系,在云栈社区与更多开发者交流你的理解与实践。