阻塞与非阻塞与多路复用的区别

先搞懂三个概念——阻塞、非阻塞、多路复用的区别

一个可能的问题是:“阻塞 IO 和非阻塞 IO 有什么区别?IO 多路复用又是什么?“——三句话 + 一张图说清。

三者的核心区别——用”等快递”一次讲透


graph TD

    subgraph 阻塞 IO Blocking IO

        A1[你打电话给快递员] --> A2[举着电话一直等]

        A2 --> A3[快递到了 挂电话]

        A4[特点:等的时候啥也不干]

    end

    subgraph 非阻塞 IO Non-blocking IO

        B1[你打电话问到了吗] --> B2[还没 挂了]

        B2 --> B3[过一会再打]

        B3 --> B4[还没 又挂了]

        B4 --> B5[再打 到了]

        B6[特点:反复问 不卡但累]

    end

    subgraph IO 多路复用 IO Multiplexing

        C1[你雇了一个前台] --> C2[快递到了前台签收]

        C2 --> C3[前台微信通知你]

        C4[特点:前台帮你盯着<br/>来了通知你 不轮询]

    end

代码层面——三种模式各长什么样

 
"""
 
 
 
三种 IO 模式用伪代码对比——看完就知道为什么多路复用是最优解。
 
 
 
"""
 
 
 
# 问题:一个线程只能处理一个连接
 
 
 
def server_blocking():
 
 
 
    while True:
 
 
 
        client = accept()          # 卡住!等人连接
 
 
 
        data = recv(client)        # 卡住!等客户端发数据
 
 
 
        send(client, b"ok")        # 可能卡住!
 
 
 
# 问题:虽然不卡了,但需要不断轮询——白白浪费 CPU
 
 
 
def server_nonblocking():
 
 
 
    clients = []
 
 
 
    while True:
 
 
 
        new_client = accept_nonblocking()  # 不卡!没连接就返回 None
 
 
 
        if new_client:
 
 
 
            clients.append(new_client)
 
 
 
        for c in clients:                   # 要挨个问!
 
 
 
            data = recv_nonblocking(c)      # 不卡!没数据就返回 None
 
 
 
            if data:
 
 
 
                handle(data)
 
 
 
        # CPU 一直在跑这个 while True 循环——即使什么连接都没有
 
 
 
        # → CPU 空转,浪费电
 
 
 
# epoll 帮你盯着所有连接——谁有数据了就通知你,你只处理有数据的
 
 
 
def server_multiplexing():
 
 
 
    epoll = create_epoll()          # 创建一个 epoll 实例(前台)
 
 
 
    server_fd = create_server()
 
 
 
    epoll.add(server_fd, READ)      # 告诉 epoll:"帮我盯着 server,有人连就告诉我"
 
 
 
    clients = {}
 
 
 
    while True:
 
 
 
        events = epoll.wait()       # 🔴 卡住!但这里卡住是对的——
 
 
 
                                    #    没有事件时线程睡眠,不浪费 CPU
 
 
 
                                    #    有事件时立即返回——只返回就绪的 fd
 
 
 
        for fd, event in events:    # 只处理就绪的!不用挨个问!
 
 
 
            if fd == server_fd:
 
 
 
                client = accept()           # 有新连接
 
 
 
                epoll.add(client, READ)     # 也交给 epoll 盯着
 
 
 
                clients[client] = True
 
 
 
            else:
 
 
 
                data = recv(fd)             # 有数据到了
 
 
 
                if data:
 
 
 
                    handle(data)
 
 
 
                else:
 
 
 
                    # 客户端断开连接
 
 
 
                    epoll.remove(fd)
 
 
 
                    del clients[fd]
 

一张图总结三者的 CPU 利用率


graph LR

    subgraph 阻塞 IO CPU利用率极低

        A1[等] --> A2[A干活] --> A3[等] --> A4[A干活]

    end

    subgraph 非阻塞 IO CPU利用率高但浪费在问

        B1[干活] --> B2[问] --> B3[干活] --> B4[问]

    end

    subgraph 多路复用 CPU利用率刚好

        C1[睡觉] -->|被叫醒| C2[干活] -->|干完| C3[睡觉] -->|被叫醒| C4[干活]

    end


速记卡(面试闪卡)

Q1:一句话讲清「阻塞与非阻塞与多路复用的区别」到底是什么?

A:阻塞、非阻塞、IO 多路复用是三种处理 IO 等待的方式,多路复用用最少资源盯住所有连接。

Q2:阻塞 IO:举着电话一直等 —— 怎么理解?

A:像打电话死等快递:accept/recv 卡住线程,等的时候啥也不干,一个线程只能服务一个连接(Blocking IO)。

Q3:非阻塞 IO:反复问不卡但累 —— 怎么理解?

A:像每隔几秒打一次问到了没:不卡线程,但要在循环里不断轮询所有连接,CPU 空转浪费(Non-blocking IO)。

Q4:IO 多路复用:雇个前台帮你盯 —— 怎么理解?

A:像雇前台签收快递:epoll 帮你盯着所有 fd,谁有数据才通知你,没事件就睡觉不浪费 CPU(IO Multiplexing)。

Q5:三者 CPU 利用率对比 —— 怎么理解?

A:像三种排班:阻塞在等、非阻塞在问、多路复用在睡—被叫醒才干;多路复用 CPU 利用最合理,是高并发最优解(CPU Utilization)。

Q6:核心速记主线有哪些?

  • 阻塞 IO:调用即卡住,一线程一连接,利用率低

  • 非阻塞 IO:立即返回但需轮询,CPU 空转浪费

  • IO 多路复用:epoll 监听就绪事件,只处理有数据的 fd

  • 多路复用最优:无事件时睡眠、有事件才唤醒,高并发首选

口诀

A:阻塞打电话,举着不动傻等他

非阻塞轮询,CPU 空转像傻瓜

多路复用好,前台 epoll 替你盯

睡觉被叫醒,高并发里它最行

相关链接