这里写目录标题前言一、python多任务概念1.1 什么是多任务作用是什么1.2 多任务实现方式进程线程协程1.3 任务分类CPU 密集型I/O 密集型1.4 GIL 全局解释器锁二、Python 进程详解2.1 进程基础概念、实现与参数传递2.2 进程启动的底层原理与重要注意事项2.3 进程间通信Queue与底层原理三、Python 线程详解3.1 线程基础概念、实现与传参3.2 线程核心注意事项无序、守护、共享变量及安全问题3.3 互斥锁与死锁四、线程池4.1 线程池概述是什么为什么用4.2 Python 中的线程池实现4.2.1 创建线程池4.2.2 提交任务的两种方式4.2.3 获取结果、异常与回调4.2.4 关闭线程池4.3 深入原理队列 工作线程模型4.4 完整示例并发下载网页I/O 密集型五、python协程详解5.1 协程基础概念5.2 协程核心三要素5.3 协程实现与并发实战5.3.1 async 和 await协程核心语法规则async 关键字作用await 关键字使用约束与核心作用5.3.2 基础示例await 自动封装协程对象5.3.3 并发实战手动创建 Task asyncio.gather5.4 协程底层调度原理、使用注意事项及异步本质5.4.1 协程底层调度原理5.4.2 协程为什么是异步的实现方式体现在哪里六、进程 vs 线程vs 协程核心对比前言在 Python 开发中多任务编程是提升程序执行效率、充分利用 CPU 资源的核心手段而进程、线程、协程是实现多任务的方式。一、python多任务概念1.1 什么是多任务作用是什么什么是多任务多任务指同一时间内执行多个任务用户感官上的同时比如电脑同时运行微 信、浏览器、IDE就是典型的多任务场景。多任务的作用充分利用 CPU 资源避免单任务阻塞导致的效率低下大幅提升程序运行速度。多任务核心并发 vs 并行并发任务数 CPU 核心数操作系统交替执行任务单核 CPU 必为并发因 CPU 速度极快感官上是同时运行。并行任务数 ≤ CPU 核心数多个 CPU 核心真正同时执行任务效率更高。1.2 多任务实现方式进程进程是操作系统资源分配的最小单位每个进程拥有独立的内存空间和系统资源。多进程可以实现真正的并行利用多核 CPU但创建和切换开销较大进程间通信也相对复杂。在 Python 中multiprocessing模块用于实现多进程适合 CPU 密集型任务。线程线程是操作系统调度的最小执行单元同一进程内的多个线程共享内存空间因此创建和切换开销较小通信方便。但由于 Python 的 GIL全局解释器锁多线程无法实现真正的并行仅能并发执行适合 I/O 密集型任务。threading 模块提供了线程支持。协程协程是用户态的轻量级任务完全由程序自身控制切换如 async/await无需操作系统参与因此切换开销极低单线程内可轻松运行数万甚至数十万个协程。协程不存在锁竞争问题非常适合高并发的 I/O 场景。Python 的 asyncio 是协程编程的主流方案。1.3 任务分类CPU 密集型任务主要消耗 CPU 计算资源如循环计算、图像处理、模型训练提升效率的关键是充分利用多核并行适合采用多进程。I/O 密集型任务主要花费时间在等待输入输出如文件读写、网络请求、数据库查询提升效率的关键是减少阻塞等待适合采用多线程或协程。1.4 GIL 全局解释器锁GILGlobal Interpreter Lock全局解释器锁是 CPython 解释器中的一个互斥锁它确保同一时刻只有一个线程执行 Python 字节码。这意味着即使有多个 CPU 核心python多线程也无法实现并行执行字节码只能是并发。需要说明的是线程内部执行分为用户态和内核态内核态就是当字节码执行到需要操作系统服务时如打开文件、发送网络数据、创建线程解释器会调用 C 库函数最终触发系统调用线程执行从用户态切换到内核态。在进入内核态、且系统调用已经发起之后CPython 解释器会主动释放 GIL让其他等待中的线程有机会获取 GIL 并执行字节码。与此同时当前线程继续在内核态运行操作系统代码若该操作需要等待外部数据如网络响应尚未到达操作系统会将该线程挂起阻塞并让出 CPU 时间片直到数据就绪后被重新唤醒。因此在python中执行CPU密集型任务适合用多进程在执行IO密集型任务时适合多线程GIL对IO密集型任务的影响比较小。二、Python 进程详解2.1 进程基础概念、实现与参数传递进程是操作系统资源分配的最小单位一个运行的程序如 Python 脚本就是一个进程每个进程拥有独立的内存空间和系统资源。进程内至少有一个主线程也可以创建多个子线程。多进程即通过主进程创建子进程实现多任务的并行或并发。实现步骤multiprocessing 模块import multiprocessing创建进程对象p multiprocessing.Process(target任务函数, args/kwargs, name进程名)启动子进程p.start()参数传递支持元组和字典两种方式p1multiprocessing.Process(targetcoding,args(3,小明))# 元组传参p2multiprocessing.Process(targetmusic,kwargs{count:2,name:小明})# 字典传参获取进程编号用于管理和调试当前进程 IDos.getpid() 或 multiprocessing.current_process().pid父进程 IDos.getppid()2.2 进程启动的底层原理与重要注意事项启动原理Windows默认 spawn 模式调用Process.start()会调用系统 API 生成全新子进程启动独立 Python 解释器执行python -c加载spawn_main入口脚本。父进程通过管道序列化传递目标函数与参数子进程通过该脚本读取数据后导入当前脚本执行指定任务。Linux/macOS默认 fork 模式底层直接调用系统fork()复制父进程内存空间不新建解释器无需执行额外 python 命令。子进程拷贝父进程运行状态后清理多余资源直接运行目标函数不会完整重新加载脚本。if__name__ __main__作用两种模式下子进程都会执行脚本顶层代码。若创建进程的代码未放入该判断块spawn 模式会反复新建解释器递归创建进程fork 模式也会持续复制子进程最终出现无限创建进程、程序卡死的问题因此进程实例化代码必须写在此分支内。三大注意事项进程间不共享全局变量子进程是主进程的资源副本同名全局变量互不影响。主进程默认等待所有子进程结束会等子进程执行完毕再退出。让主进程不等待子进程的方法守护进程推荐p.daemon True主进程退出时子进程直接销毁。手动终止不推荐p.terminate()易产生僵尸进程无法释放资源。2.3 进程间通信Queue与底层原理由于进程内存隔离无法像线程那样共享全局变量需要通过multiprocessing.Queue实现数据传递。frommultiprocessingimportProcess,Queuedefwrite(q):foriin[a,b,c]:q.put(i)defread(q):whilenotq.empty():print(q.get())if__name____main__:qQueue()p1Process(targetwrite,args(q,))p2Process(targetread,args(q,))p1.start()p1.join()# 等待写进程结束p2.start()底层原理multiprocessing.Queue内部基于操作系统的管道或消息队列实现。当进程 A 放入数据时数据先被 pickle 序列化 成字节流然后拷贝到操作系统内核空间的一块缓冲区进程 B 取出数据时内核将这份数据拷贝到进程 B 的内存中再反序列化还原为对象。整个过程两个进程无法直接访问对方内存全靠操作系统充当可靠的中介。关于锁机制虽然两个进程拥有各自内存中的 Queue 实例但这些实例内部的_reader、_writer管道文件描述符以及_lock跨进程锁都指向操作系统中相同的内核对象共享资源。_lock是为了保护对管道缓冲区的并发访问防止写入数据混乱。实际上 Queue 内部不止一把锁还包括用于同步读取和写入的条件变量等但核心思想就是跨进程共享的资源必须通过系统级的锁来保证原子操作。进程A(生产者)操作系统内核 进程B(消费者)|||1. put(obj)||- 获取锁(Lock)||- pick序列化 obj -bytes||- 把bytes放入内部缓冲区|||||2. 后台线程运行||- 从缓冲区取出bytes||- 调用 write(pipe_fd, bytes)---[管道缓冲区]|- 释放锁|||||||数据准备好||||||3. get()进程B被唤醒||- 获取锁||- 调用 read(pipe_fd)---- 读数据 反序列化释放锁||- 返回 obj三、Python 线程详解3.1 线程基础概念、实现与传参线程是 CPU 调度的最小单位依附于进程存在。一个进程默认有一个主线程可创建多个子线程线程间共享进程的所有资源。多线程的资源开销远小于进程适合 I/O 密集型任务。实现步骤threading 模块import threading创建线程对象t threading.Thread(target任务函数, args/kwargs)启动线程t.start()传参方式与进程完全相同支持元组args(3, 小红)和字典kwargs。3.2 线程核心注意事项无序、守护、共享变量及安全问题线程执行无序由 CPU 调度决定与创建顺序无关多次运行结果可能不同。主线程默认等待子线程结束可设置守护线程让主线程退出时销毁子线程tthreading.Thread(targetwork,daemonTrue)# 方法1t.setDaemon(True)# 方法2线程间共享全局变量这是线程的核心优势无需额外通信机制。共享变量导致数据安全问题多线程同时修改同一全局变量会产生资源竞争导致计算结果错误。3.3 互斥锁与死锁互斥锁保证同一时刻只有一个线程操作共享数据。步骤创建锁 →acquire()上锁 →release()解锁。importthreading g_num0mutexthreading.Lock()def sum_num(): global g_num mutex.acquire()foriinrange(1000000): g_num1mutex.release()print(f结果{g_num})t1threading.Thread(targetsum_num)t2threading.Thread(targetsum_num)t1.start();t2.start()死锁问题多个线程互相等待对方释放锁导致程序卡死。常见原因上锁后未释放、嵌套锁顺序不当。解决方法保证锁的成对释放避免嵌套锁或使用 with 语句自动释放。lock1threading.Lock()lock2threading.Lock()deffunc1():lock1.acquire()time.sleep(0.1)lock2.acquire()# 等待 lock2但 lock2 可能被 func2 持有lock2.release()lock1.release()deffunc2():lock2.acquire()time.sleep(0.1)lock1.acquire()# 等待 lock1但 lock1 被 func1 持有lock1.release()lock2.release()四、线程池4.1 线程池概述是什么为什么用什么是线程池线程池是一种 池化技术预先创建一组可复用的线程任务到来时分配给空闲线程执行执行完后线程不销毁而是等待下一个任务。为什么需要线程池降低资源消耗复用已存在的线程减少频繁创建/销毁的开销。提高响应速度任务到达时免去线程创建时间。便于统一管理控制最大并发数避免线程过多导致系统崩溃。线程池 vs. 手动创建线程对比项手动创建线程线程池创建/销毁开销高每个任务都要创建和销毁线程低线程复用资源控制难以控制最大线程数可以设置最大线程数代码复杂度需要自行管理线程生命周期和异常简洁只需提交任务4.2 Python 中的线程池实现python中主要使用concurrent.futures.ThreadPoolExecutor类实现线程池的创建和使用4.2.1 创建线程池fromconcurrent.futuresimportThreadPoolExecutor# 默认线程数min(32, CPU核心数4)executorThreadPoolExecutor()# 自定义最大线程数常用executorThreadPoolExecutor(max_workers5)# 还可以指定线程名前缀、初始线程Python 3.8executorThreadPoolExecutor(max_workers5,thread_name_prefixMyPool)核心参数max_workers线程池中最多同时运行的线程数。并非越多越好建议根据 IO 密集型任务 的性质设置适当值如 CPU 核心数 * 5 或更高。thread_name_prefix为线程池中的线程命名方便调试。4.2.2 提交任务的两种方式submit(fn, *args, **kwargs)—— 提交单个任务futureexecutor.submit(print,Hello)# future 是一个 Future 对象代表异步操作的句柄map(func, *iterables, timeoutNone)—— 批量提交defsquare(n):returnn*nwithThreadPoolExecutor()asexecutor:resultsexecutor.map(square,[1,2,3,4,5])forrinresults:print(r)# 输出顺序与输入顺序一致4.2.3 获取结果、异常与回调Future.result(timeoutNone)阻塞等待结果可设置超时任务异常时此处会抛出。Future.done() / running() / cancelled()等用于状态查询。add_done_callback(fn)任务完成后自动调用回调函数在任务线程中执行注意线程安全。defcallback(future):print(f结果:{future.result()})futureexecutor.submit(pow,2,10)future.add_done_callback(callback)4.2.4 关闭线程池# 方法一等待所有任务完成并禁止新任务executor.shutdown(waitTrue)# waitTrue 为默认阻塞直到全部完成# 方法二使用上下文管理器推荐withThreadPoolExecutor(max_workers5)asexecutor:executor.submit(func,arg)# 离开 with 块时自动调用 shutdown(waitTrue)4.3 深入原理队列 工作线程模型ThreadPoolExecutor的核心是一个生产者‑消费者模型一个任务队列queue.Queue主线程通过submit()将任务包装成(fn, args, kwargs, future)放入队列。一组工作线程线程池在初始化时不会立即创建所有线程而是懒创建——当提交第一个任务时如果当前线程数不足max_workers就创建新线程每个工作线程不断从队列中取任务执行执行完后继续取下一个永不退出除非收到关闭信号。线程池里的线程默认都是守护线程关闭线程池时可以通过wait参数指定关闭线程池的行为是等所有线程都完成还是让守护线程生效。classThreadPoolExecutor:def__init__(self,max_workers):self._work_queuequeue.Queue()self._threadsset()self._max_workersmax_workers self._shutdownFalsedefsubmit(self,fn,*args,**kwargs):ifself._shutdown:raiseRuntimeError(cannot schedule new futures)fFuture()self._work_queue.put((fn,args,kwargs,f))# 懒创建线程iflen(self._threads)self._max_workers:tthreading.Thread(targetself._worker)t.daemonTrueself._threads.add(t)t.start()returnfdef_worker(self):whileTrue:itemself._work_queue.get()ifitemisNone:# 毒丸信号breakfn,args,kwargs,futureitemtry:resultfn(*args,**kwargs)future.set_result(result)exceptExceptionase:future.set_exception(e)# 线程退出前清理self._threads.discard(threading.current_thread())defshutdown(self,waitTrue):self._shutdownTruefor_inrange(len(self._threads)):self._work_queue.put(None)# 让所有工作线程退出ifwait:fortinself._threads:t.join()4.4 完整示例并发下载网页I/O 密集型并发下载多个网页IO 密集型importrequestsfromconcurrent.futuresimportThreadPoolExecutor,as_completed urls[https://example.com,https://httpbin.org/get,https://www.python.org]deffetch(url):resprequests.get(url,timeout5)returnurl,resp.status_codewithThreadPoolExecutor(max_workers3)asexecutor:future_to_url{executor.submit(fetch,url):urlforurlinurls}forfutureinas_completed(future_to_url):urlfuture_to_url[future]try:url,statusfuture.result()print(f{url}-{status})exceptExceptionase:print(f{url}失败:{e})五、python协程详解5.1 协程基础概念什么是协程协程是用户态的轻量级任务完全由程序自身控制切换无需操作系统参与。多个协程可以在一个线程内并发执行遇到 I/O 等待时主动让出 CPU事件循环调度其他就绪协程继续运行。为什么需要协程极高并发一个线程可轻松创建数万甚至数十万个协程内存开销极小每个协程仅数百字节。无锁编程协程间切换不涉及共享资源竞争单线程内串行执行无需费力处理锁。5.2 协程核心三要素协程的运行依赖 3 个核心对象这是理解协程的关键三者的转换关系必须牢记核心对象定义核心作用能否直接被调度事件循环Event Loop协程的调度中心管理所有任务监听 IO 状态自动切换可执行任务-协程对象Coroutineasync 函数调用后生成的对象封装协程逻辑是任务的原始载体❌ 不能Task 任务协程对象的封装体继承自 Future事件循环的最小调度单位支持并发✅ 能三者转换规则协程函数 → 协程对象用async def定义协程函数直接调用函数不会执行只会生成协程对象协程对象 → Task 对象显式转换推荐task asyncio.create_task(coro)获得 Task 句柄可控制、查询状态隐式转换await coro时事件循环内部自动包装为 Task并注册到事件循环但无法获得引用Task 对象 → 事件循环调度执行事件循环运行 TaskTask 执行到await时主动挂起等待事件I/O、定时器。事件循环在此期间调度其他 Task待事件发生后恢复该 Task 的执行。5.3 协程实现与并发实战5.3.1 async 和 await协程核心语法规则Python 协程基于asyncio模块核心依靠async和await两个关键字同时内置隐式转换逻辑。async 关键字作用用于修饰普通函数将其定义为协程函数协程函数直接调用不会执行内部业务逻辑仅返回一个协程对象协程对象只是逻辑载体本身无法被事件循环直接调度。await 关键字使用约束与核心作用使用约束只能在 async 修饰的协程函数内部使用普通同步函数中禁止出现核心作用暂停当前协程后续代码主动挂起当前协程让出执行控制权给事件循环用于调度其他协程任务等待后方异步 I/O 操作完成I/O 就绪后恢复当前协程继续执行是协程实现单线程多任务切换、异步并发的核心支撑。后置对象隐式转换规则await 后面可接收协程对象、Task 对象、Future 对象底层自动处理转换若后置是裸协程对象asyncio 底层会自动将其封装为 Task 任务注册到事件循环参与调度无需手动写create_task()若已经手动通过create_task()封装为 Task直接等待任务执行完毕不会重复封装场景选用简单串行逻辑直接 await 协程对象 即可需要并发、获取任务状态、设置回调时必须手动创建 Task。5.3.2 基础示例await 自动封装协程对象importasyncio# 定义协程函数asyncdefwork(name):print(f任务{name}开始执行)# await 后接协程对象底层自动转为 Task 调度awaitasyncio.sleep(1)print(f任务{name}执行完毕)# 主协程入口asyncdefmain():# 调用协程函数生成协程对象corowork(测试任务)# await 自动把协程对象封装为 Task 并执行awaitcoro# 启动事件循环运行主协程asyncio.run(main())5.3.3 并发实战手动创建Task asyncio.gather单纯顺序 await 多个协程只是串行执行无法发挥协程并发优势想要真正并发必须手动把协程对象封装为 Task再通过asyncio.gather()批量调度。importasyncioasyncdefwork(name,delay):print(f任务{name}开始执行等待{delay}s)awaitasyncio.sleep(delay)print(f任务{name}执行完毕)returnf{name}完成asyncdefmain():# 手动将协程对象封装为 Task加入事件循环待命task1asyncio.create_task(work(A,2))task2asyncio.create_task(work(B,1))# 并发等待所有 Task 执行完成resultsawaitasyncio.gather(task1,task2)print(所有任务执行结果,results)# 总耗时约 2s实现真正并发asyncio.run(main())5.4 协程底层调度原理、使用注意事项及异步本质5.4.1 协程底层调度原理调度模式协作式调度非抢占式进程、线程是操作系统强制抢占式切换协程没有操作系统参与是任务遇到 await 主动让出控制权属于用户态协作切换除非协程主动让出控制权否则当前协程一直占用。完整切换流程协程执行到 await 触发 IO 等待 → 主动挂起释放事件循环控制权事件循环检索就绪队列调度其他可运行的 Task 协程原有 IO 操作完成后事件循环重新唤醒原协程继续执行后续代码。性能优势全程在用户态完成切换无操作系统上下文切换开销资源消耗极低。5.4.2 协程为什么是异步的实现方式体现在哪里异步核心特征非阻塞、等待不占用资源普通同步代码遇到 IO 会阻塞整个线程原地空等占用资源协程遇到 await 触发 IO 操作时异步IO操作仅瞬间完成请求发起、向事件循环注册监听事件随后主动挂起并让出协程控制权不会原地阻塞死等事件循环可调度执行其他任务实现 IO 等待时段的资源复用。调度异步化由事件循环统一调度多个协程任务执行顺序不由代码编写顺序决定而是由 IO 就绪状态动态唤醒调度完全契合异步编程调度思想。IO 等待可并行重叠所有异步网络请求、数据库读写等真实 IO 任务底层逻辑和 asyncio.sleep 完全一致多个协程无需串行挨个等待IO 等待过程交由操作系统后台并行处理等待时间相互重叠这是协程异步高并发、省时间的根本原因。六、进程 vs 线程vs 协程核心对比对比维度进程线程协程调度方操作系统操作系统用户程序自身资源开销极大中等极小内存共享不共享共享完全共享切换开销高中极低锁竞争无有无多核利用✅ 支持❌ 受 GIL 限制❌ 单线程并发能力低中极高单机万级适用场景CPU 密集型IO 密集型高并发 IO 密集型