找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2347

积分

0

好友

315

主题
发表于 8 小时前 | 查看: 0| 回复: 0

还在被同步、异步、阻塞、非阻塞、进程、线程、协程、并发、并行这些概念绕晕吗?看完这篇,或许你会发现它们并没有想象中那么复杂。

许多后端开发者在面试或学习时,常常被问到这些看似相似却又不同的概念:

  • “同步和异步有什么区别?”
  • “阻塞和非阻塞是一回事吗?”
  • “进程、线程、协程到底啥关系?”
  • “并发和并行能一样吗?”

听起来熟悉?别慌!今天我们用最通俗的方式,配合图解和代码,系统性地厘清这些让人头疼的核心概念。

一、核心概念速览

先上一张全局关系图,帮助你建立整体认知:

┌─────────────────────────────────────────────────┐
│            应用程序 (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多路复用(同步) = 用一个阻塞调用同时监听多个文件描述符

为什么容易混淆?

  1. epoll_wait()本身确实会阻塞,等待事件发生
  2. 但监听的socket通常设为非阻塞模式
  3. 它能单线程高并发(看起来像异步),但本质是同步的快速切换

真正的异步非阻塞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);
}

为什么说异步阻塞低效且罕见?

  1. 矛盾:发起异步操作是为了不阻塞,但最后又阻塞等待
  2. 效率低:既然要阻塞等待结果,不如直接用同步I/O,代码更简单
  3. 唯一好处:可以同时发起多个异步操作,然后统一等待(批量处理)

真实应用场景(少见):

// 同时读取多个文件,然后一起等待
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方案

💡 最佳实践

  1. 默认用同步阻塞,除非有性能瓶颈
  2. IO密集用IO多路复用,CPU密集用多线程/进程
  3. 协程适合高并发场景(百万连接)
  4. 事件循环+epoll是主流高性能方案(Nginx、Redis、Node.js)
  5. 真正需要异步时用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: 任务关系                       │
│  • 并发: 交替执行(单核可实现)          │
│  • 并行: 同时执行(需多核)             │
├──────────────────────────────────────┤
│ 异步机制                              │
│  • 回调: 完成时调用函数               │
│  • 事件循环: 不断检查和处理事件        │
└──────────────────────────────────────┘

结语

这些概念看起来复杂,其实只要抓住核心维度,就能轻松理解:

  1. 同步/异步关注执行顺序
  2. 阻塞/非阻塞关注等待状态
  3. 进程/线程/协程是资源粒度
  4. 并发/并行是任务关系
  5. 回调/事件循环是实现机制

记住这个框架,以后再也不会混淆了!希望这篇深度解析能帮助你构建清晰的知识体系,在云栈社区与更多开发者交流你的理解与实践。




上一篇:jQuery 4.0 正式发布:首个十年大版本更新与现代化重构详解
下一篇:产品经理视角:为何感知不到AI Coding带来的研发提效?
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 16:34 , Processed in 0.243700 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表