Python-模块篇(并发编程篇、threading、multiprocessing、asyncio)

1、关键概念

  • 并发 (Concurrency) vs 并行 (Parallelism)
    • 并发:宏观上同时处理多件事(threading 和 asyncio),微观上可能同一时刻只做一个。(交替处理多个任务)
    • 并行:微观上真正同时做多件事(multiprocessing),需要多核CPU支持。(同时处理多个任务)
  • GIL(全局解释器锁)的通俗解释
    • Python的 "大锁" ,让一个进程同一时间只能执行一个线程。
    • 所以threading 做不了真正的并行计算,而 multiprocessing 通过独立进程绕过了它。(线程执行前必须先获得GIL锁)
  • 同步 vs 异步
    • 同步:做完一件事再做下一件。(排队买票)
    • 异步:发起一件事后不等待,先去处理别的,有结果了再回来处理。(点餐后干其它的,等菜上好再吃饭)
  • 进程(Process) vs 线程(Thread)
    • 进程:正在执行的程序,是CPU、内存等资源分配的基本单位
    • 线程:包含在进程中,实际是CPU调度执行具体任务的最小单位

2、适合场景

并发方式 别名 适合场景 总结
threading 多线程

I/O密集型任务(网络请求、文件读写)

1、同时只能使用CPU的一个核心

任务数量适中(几十到几百)

轻量级,切换快(线程之间能共享资源),但要注意线程安全。

1、安全问题:多个线程同时操作一个共享数据时,可能导致数据不准确

2、解决:代码加锁(threading.Lock())

3、死锁:两个线程都在等待获取对方的锁,导致程序暂停

multiprocessing 多进程

CPU密集型任务(复杂计算、图像处理)

1、充分利用多核CPU

资源开销大(进程之间不能共享资源),但能绕过GIL,实现真正的并行。

asyncio 异步I/O

高并发I/O密集型任务

(任务数量巨大(成千上万))

(大量网络爬虫、高性能Web服务器)

单线程内实现高并发,资源消耗极低,但代码有侵入性(async/await)。

1、侵入性:使用(async/await)实现异步,需要对原代码的调用链、逻辑结构和运行逻辑进行调整

3、threading 模块:轻量级并发

  • 注意:修改共享变量时记得加锁(threading.Lock()),防止数据错乱。
  • 函数式多线程:适合只执行一个函数,不需要保存线程状态。
  • 类式多线程:适合需要为每个线程保存独立的属性(如 result、passed、error_msg)。
import threading
import time

# 方式1:直接传入函数
def request_api(url):
    print(f"正在请求: {url}")
    time.sleep(2)  # 模拟I/O等待
    print(f"请求完成: {url}")

threads = []
for url in ["url1", "url2", "url3"]:
    #创建线程
    t = threading.Thread(target=request_api, args=(url,))
    threads.append(t)
    t.start() #创建并启动一个新的线程,新线程会并发执行 target 函数

for t in threads:
    t.join()  # 主线程等待所有线程完成(防止主线程提前退出)
print("所有请求完成")
import threading
import time

# 方式2:类式多线程
# 必须继承threading.Thread
class RequestThread(threading.Thread): """一个模拟请求的线程类""" def __init__(self, url, timeout=30): # 必须调用父类的 __init__ 方法 super().__init__() self.url = url self.timeout = timeout self.result = None # 用于存储执行结果 #重写run(),实际是多线程执行的target函数 def run(self): """线程执行的核心逻辑,start() 后会自动调用此方法""" print(f"线程 {self.name} 开始请求: {self.url}") time.sleep(2) # 模拟I/O等待 # 模拟返回结果 self.result = f"响应数据 from {self.url}" print(f"线程 {self.name} 请求完成") # 使用示例 if __name__ == "__main__": urls = ["http://api1.com", "http://api2.com", "http://api3.com"] threads = [] # 1. 创建并启动所有线程 for url in urls: t = RequestThread(url) #理解每个对象为1个线程 threads.append(t) t.start() # start() 会自动调用 run() # 2. 等待所有线程完成 for t in threads: t.join() # 3. 收集结果 results = [t.result for t in threads] print("所有结果:", results)

4、multiprocessing 模块:绕过GIL的并行

from multiprocessing import Pool

def cpu_bound_task(n):
    """模拟CPU密集型计算,如大量数据加密"""
    print(f"进程 {n} 开始计算...")
    result = sum(i * i for i in range(10000000))
    return result

if __name__ == "__main__":
    with Pool(processes=4) as pool:  # 创建4个进程的进程池
        results = pool.map(cpu_bound_task, range(5)) #此处创建5个任务,而进程池只有4个,因此有一个任务会排队等待执行
        #p.map():批量分配任务(自动拆分 iterable,依次分配给每个进程),会阻塞主进程,直到所有子进程任务完成并返回结果
        #p.apply_async():每次只能提交一个任务给进程执行,适合需要逐个灵活提交任务的场景。
    print(results)

5、asyncio 模块:单线程高并发

  • 协程(Coroutine):一个可暂停的函数
    • async:创建协程
      • Task:协程的包装器,被事件循环调度和执行(协程本身不能并发,Task 才能并发
    • await:等待一个已有的“可等待对象”(Awaitable)完成。
  • 执行流程
1、定义协程 async def fetch_data() 定义一个可暂停的函数(协程),内部使用 await 等待异步操作。
2、调用协程 coro = fetch_data() 调用协程函数会返回一个协程对象,但不会执行其中的代码。
3、创建Task task = asyncio.create_task(coro) 将协程包装成Task并提交给事件循环,Task会立即开始调度(但未必马上执行)。
4、并发执行

results = await asyncio.gather(task1, task2)

results = asyncio.wait(task1, task2)

并发等待多个Task全部完成,并统一收集返回值。

1、asyncio.gather():主要用于等全部完成,直接返回结果列表

2、asyncio.wait():支持等任一完成等全部完成等超时等,返回 (done, pending) 任务集合

5、运行入口 asyncio.run(main())

创建并启动事件循环,运行入口协程(main()),整个异步程序的唯一入口

1、自动管理事件循环的生命周期(创建、运行、关闭)

6、暂停与恢复 result = await some_awaitable
  1. 挂起:当一个协程A执行到 await 时,它会挂起自己,保存当前状态,并将控制权交还给事件循环

  2. 切换:事件循环从就绪队列中取出下一个可执行的任务(如协程B)并执行。

  3. 唤醒:当协程A等待的I/O事件完成后,操作系统会通知事件循环。事件循环将协程A标记为就绪,并将其放回就绪队列

  4. 恢复:当事件循环再次调度到协程A时,它会从之前的 await 处恢复执行,并继续后续的逻辑。

7、获取结果 value = task.result()

获取已完成Task的返回值

#安装aiohttp:pip install aiohttp
import asyncio
import aiohttp

#使用 async 定义异步函数
async def fetch_url(session, url):
    print(f"开始请求: {url}")
    async with session.get(url) as response:
        # await 模拟等待网络响应,此时不会阻塞其他任务
        html = await response.text() 
        print(f"完成请求: {url}, 长度: {len(html)}")
        return html

async def main():
    urls = ["http://httpbin.org/get", "http://httpbin.org/ip", "http://httpbin.org/user-agent"]
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        #asyncio.gather 并发执行多个任务。
       #gather 接收到协程对象时,会自动调用 create_task() 将其包装成 Task
        results = await asyncio.gather(*tasks) #
        return results

# 运行主协程
if __name__ == "__main__":
    asyncio.run(main())

 

posted @ 2026-07-09 17:37  FengweiTech  阅读(12)  评论(0)    收藏  举报