Skip to content

第一部分:并发编程基础理论


1. 并发编程概述

1.1 并发编程的意义与目标

并发编程(Concurrent Programming)的核心目标是提高程序执行效率,充分利用多核 CPU 资源,使多个任务能够在同一时间段内得到处理,从而提升计算机资源的整体利用率。

并发编程并不仅仅意味着“同时执行”,其本质在于任务的合理调度与资源的高效利用。在单核 CPU 上,也可以通过任务切换实现“看似同时执行”的效果。通过并发机制,程序能够在等待 I/O 操作期间执行其他任务,从而提高吞吐量与响应性能。


2. 同步与异步、阻塞与非阻塞

理解“同步 / 异步”与“阻塞 / 非阻塞”是掌握并发编程的基础。这四个概念描述了任务在等待和执行过程中的行为模式。

2.1 同步与异步

模式执行逻辑是否等待结果示例
同步(Synchronous)当前任务完成后才能继续执行下一个任务普通函数调用
异步(Asynchronous)任务发出后可继续执行其他操作,无需等待完成异步 I/O、回调机制
  • 同步:任务顺序执行,一个完成后才能进行下一个。
  • 异步:任务之间可交替执行,无需等待上一个任务的结果。

2.2 阻塞与非阻塞

模式CPU 状态是否等待操作完成示例
阻塞(Blocking)CPU 空闲requests.get()
非阻塞(Non-blocking)CPU 可执行其他任务非阻塞 socket、异步请求
  • 阻塞:当任务等待外部事件(如网络响应)时,CPU 处于空闲状态。
  • 非阻塞:等待期间,CPU 可以继续执行其他任务。

2.3 四种组合模式

模式执行机制CPU 利用率效率常见场景
同步阻塞顺序执行,等待时 CPU 空闲最低普通网络请求
异步阻塞多线程并发,但调用仍阻塞较低多线程下载
同步非阻塞主动轮询检查结果非阻塞 socket
异步非阻塞异步调度,完全非阻塞执行最高asyncioaiohttp

2.4 示例代码

以下示例展示了四种执行模式的差异。

python
import time
import requests
import aiohttp
import asyncio
import socket
import threading

# ===============================
# 1. 同步阻塞 (Synchronous Blocking)
# ===============================
def sync_blocking_example():
    """同步阻塞示例:依次请求多个URL"""
    urls = ['http://httpbin.org/delay/1'] * 3
    start = time.time()
    for url in urls:
        resp = requests.get(url)
        print(f"[Sync Blocking] Got response: {resp.status_code}")
    print(f"Total time: {time.time() - start:.2f}s")  # 约3秒


# ===============================
# 2. 异步阻塞 (Asynchronous Blocking)
# ===============================
def async_blocking_example():
    """异步阻塞示例:多线程并发但阻塞I/O"""
    urls = ['http://httpbin.org/delay/1'] * 3
    start = time.time()

    def fetch(url):
        resp = requests.get(url)
        print(f"[Async Blocking] Got response: {resp.status_code}")

    threads = [threading.Thread(target=fetch, args=(u,)) for u in urls]
    [t.start() for t in threads]
    [t.join() for t in threads]

    print(f"Total time: {time.time() - start:.2f}s")  # 约1秒


# ===============================
# 3. 同步非阻塞 (Synchronous Non-blocking)
# ===============================
def sync_nonblocking_example():
    """同步非阻塞示例:非阻塞socket轮询"""
    start = time.time()
    host, port = "httpbin.org", 80
    s = socket.socket()
    s.setblocking(False)
    try:
        s.connect((host, port))
    except BlockingIOError:
        pass  # 非阻塞下连接未立即建立

    while True:
        try:
            s.send(b"GET /delay/1 HTTP/1.0\r\nHost: httpbin.org\r\n\r\n")
            break
        except BlockingIOError:
            continue  # 轮询等待

    resp = b""
    while True:
        try:
            chunk = s.recv(4096)
            if not chunk:
                break
            resp += chunk
        except BlockingIOError:
            continue
    print(f"[Sync Non-blocking] Response length: {len(resp)}")
    print(f"Total time: {time.time() - start:.2f}s")
    s.close()


# ===============================
# 4. 异步非阻塞 (Asynchronous Non-blocking)
# ===============================
async def async_nonblocking_example():
    """异步非阻塞示例:aiohttp + asyncio"""
    urls = ['http://httpbin.org/delay/1'] * 3
    start = time.time()
    async with aiohttp.ClientSession() as session:
        tasks = [session.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
        for r in responses:
            print(f"[Async Non-blocking] Got response: {r.status}")
            await r.text()
    print(f"Total time: {time.time() - start:.2f}s")  # 约1秒


if __name__ == "__main__":
    print("=== 1. 同步阻塞 ===")
    sync_blocking_example()

    print("\n=== 2. 异步阻塞 ===")
    async_blocking_example()

    print("\n=== 3. 同步非阻塞 ===")
    sync_nonblocking_example()

    print("\n=== 4. 异步非阻塞 ===")
    asyncio.run(async_nonblocking_example())

结果对比表:

模式预计耗时特征
同步阻塞≈ 3s顺序执行,CPU 空闲
异步阻塞≈ 1s多线程并发但阻塞 I/O
同步非阻塞≈ 1.2s轮询方式,CPU 占用较高
异步非阻塞≈ 1s完全异步调度,效率最高

3. 串行、并发与并行

模式描述特点
串行(Serial)任务依次执行,一个任务完成后才开始下一个任务实现简单但效率较低
并发(Concurrent)多个任务在同一核心上交替执行单核可实现,看似同时执行
并行(Parallel)多个任务在多个 CPU 核心上同时执行真实意义上的“同时”运行

并发是“任务调度策略”,并行是“任务执行状态”。


4. 实现多任务的三种方式

4.1 进程(Process)

  • 定义:进程是操作系统分配资源的基本单位,拥有独立的内存空间、数据段、代码段和堆栈。
  • 特点
    • 进程间相互独立;
    • 创建和销毁开销较大;
    • 通信需通过 IPC(如管道、消息队列、共享内存)。
  • 适用场景:独立任务、CPU 密集型任务。

4.2 线程(Thread)

  • 定义:线程是进程内最小的执行单元,共享进程资源(内存、文件描述符等)。
  • 特点
    • 创建销毁成本较低;
    • 共享内存空间,通信高效;
    • 需要同步机制(如锁)防止数据竞争;
    • 在 Python 中受 GIL 限制。
  • 适用场景:相关性强的并发任务、轻度 I/O 操作。

4.3 协程(Coroutine)

  • 定义:协程是用户态的轻量级线程,由程序控制切换。
  • 特点
    • 切换开销极小;
    • 单线程即可实现并发;
    • 适合 I/O 密集型任务;
    • 不适合 CPU 密集任务。
  • 适用场景:大量网络请求、文件读写等 I/O 密集操作。

5. 三种方式对比

特性进程线程协程
调度单位操作系统操作系统用户态(语言层)
内存空间独立共享共享
创建/销毁成本
并发性能较好较好最优(I/O 场景)
通信方式IPC共享内存共享内存
适用场景独立任务 / CPU 密集相关任务 / 轻 I/O大量 I/O 密集
CPU 利用真正并行受 GIL 限制单线程并发

6. 适用场景与选择策略

任务类型推荐方式优点缺点
CPU 密集型多进程真正并行执行内存占用较高
I/O 密集型(少量)多线程实现简单受 GIL 限制
I/O 密集型(大量)协程高并发、低开销无法多核并行
混合型任务进程 + 协程综合利用 CPU 与 I/O实现复杂

7. 总结

  • 并发编程旨在通过合理的调度机制提高资源利用率;
  • “同步 / 异步”“阻塞 / 非阻塞”定义了任务在等待与执行过程中的行为;
  • “进程 / 线程 / 协程”是三种主要的多任务实现方式;
  • CPU 密集型任务使用多进程I/O 密集型任务使用协程
  • 并发是任务的组织方式,并行是任务的执行方式。

总结要点:

并发编程的核心是利用任务切换机制,提高计算机系统在多任务场景下的整体效率。


邬东升的博客