Python进阶 多线程、生成器与协程

Python进阶 多线程、生成器与协程

一. 多线程入门

1. 重点进程、线程与主线程
  • 进程CPU资源分配的最小单位;创建一个进程就会分得一定资源。
  • 线程CPU 调度 的基本单位,依附于进程 执行代码;每个进程至少有一个线程 ,通常就是主线程
  • 多任务 :在 Python 里除多进程 外,还可以在同一进程 里用多线程完成多任务;写法和多进程很像,主要是模块名、类名不同。
2. 多线程三步走
  • 导入线程模块import threading

  • 创建线程对象线程对象 = threading.Thread(target=任务函数名)(还可传 argskwargsname 等)

  • 启动线程线程对象.start()

  • target=函数名 ,不要写成 函数()

  • 对比多进程multiprocessing.Process 换成 threading.ThreadProcess(...) 换成 Thread(...),同样是 targetargskwargsstart()

  • if __name__ == '__main__': :多进程在 Windows 上必须 这样写,防止子进程重复加载模块;多线程一般不强制 ,但建议也写上,结构清楚。

3. Thread 参数与线程名
  • args元组 按位置传参,顺序要和任务函数的形参顺序 一致;只有一个参数时写成 (x,)
  • kwargs字典 按关键字传参,字典的 key 必须和任务函数的形参名字一致(与名字对应,不是"猜顺序")。
  • name :线程名,可不写;默认如 Thread-1Thread-2
  • 当前线程threading.current_thread()名字threading.current_thread().name(主线程常见为 MainThread)。
  • 线程编号threading.current_thread().ident(解释器内编号)、threading.current_thread().native_id(操作系统线程编号),需要时使用。
4. 线程执行顺序
  • 多个线程谁先执行、谁先打印CPU 调度 决定,没有固定顺序;多次运行顺序可能不同,属于正常现象。
  • 进程 之间被谁调度执行同样具有无序性(操作系统调度)。
  • 线程需要先后关系 时,可用 join()、互斥锁或线程同步 等手段配合,不能默认"先 start 就先执行完"。

二. 共享全局变量与守护线程

1. 线程之间共享全局变量
  • 同进程内的线程共享同一块内存 ,因此可以共享全局变量进程之间不共享全局变量 (各是各的内存),这是进程和线程的核心区别之一
  • 验证思路 :多个子线程里打印进程号 (如 os.getpid()multiprocessing.current_process().pid),相同说明同一进程;一个线程写列表、另一个线程读列表,能读到说明共享。
  • global :对全局变量 做原地修改(如 my_list.append)常不必global;对全局变量重新赋值 (如给不可变类型 countcount = count + 1)要在函数里写 global count
2. 主线程等待与守护线程
  • 默认主线程会等待所有子线程执行结束后,程序才结束(子线程还在跑时,进程往往不会提前退出)。
  • 守护线程
    • 创建时传入:threading.Thread(target=..., daemon=True)
    • 或:先创建线程对象,在 start() 之前 设置 线程对象.daemon = True
    • 另有一种 setDaemon(True) 方法,属于旧写法,能见到即可,优先用上面两种
  • 线程同步 / 数据安全 问题见下一章;进程 里还有 terminate() 等,线程 侧一般不那样结束,主要靠守护或业务上自行结束。

三. 线程安全、互斥锁与 GIL

1. 共享全局变量带来的数据错误
  • 两个线程都对同一全局变量做大量 +1 时,结果可能远小于 理论值(例如各加 100 万次却得不到 200 万),因为多线程同时读写 时,读-改-写 可能被拆开交错执行,造成丢更新
  • PPT 说明 :解释器较新版本上有时现象不如** Python 3.8** 等设备上明显,课堂演示可能指定版本观察。
2. 线程同步与互斥锁
  • 线程同步 :让多个线程按约定先后 访问共享资源,避免乱抢。互斥锁 是常用手段:同一时刻只有一个线程能进入"上锁"的那段代码。

  • 互斥锁 :对共享数据 加锁,多个线程一起抢锁 ,抢到的先执行,其它等待 ;用完后释放锁,其它线程再抢。

  • 使用流程threading.Lock() 创建锁 → acquire() 上锁 → 操作共享变量 → release() 释放锁。也可用 with lock: ,离开代码块时自动释放,减少忘记 release 导致死锁

python 复制代码
lock = threading.Lock()

def task():
    with lock:
        # 同一时间只有一个线程能执行这里
        pass
  • 多线程必须用同一个锁对象 (同一个 lock 传入或共用一个全局锁),每个人 Lock() 一把新锁等于没互斥
3. 死锁
  • 含义 :线程一直等对方释放锁 ,程序卡住、无法继续
  • 常见原因 :上锁后没有在合适地方释放、锁嵌套不当等。
4. join 与互斥锁
  • PPT 结论 :线程共享全局变量出现竞争时,除互斥锁外,join() 也是一种思路,本质容易把并行拉成串行,简单但可能牺牲效率。
  • 锁的粒度 :锁包住整段循环 时更接近单任务;锁在循环内部 时仍可能看到线程交替 ,但最终以共享数据是否正确为准。
5. GIL 与 threading.Lock
  • GIL(Global Interpreter Lock)CPython隐式 的全局互斥,同一时刻通常只有一条线程在执行 Python 字节码 ;多核上纯 Python 计算密集型 任务往往难以真正多核并行
  • threading.Lock :你自己创建的显式 互斥锁,保护你的共享变量
  • 为什么有 GIL 还要 Lock? 一行源码可能对应多条字节码 ,字节码之间仍可能切换线程,业务上的"读-改-写"仍会被打断,所以该加锁还要加锁。
  • PPT 补充 :GIL 来自早期 CPython 设计(降低开销与死锁风险等),当时多核不普及;如今生态依赖多,CPython 仍保留 GIL多进程、**异步(async)**等是常见补充方案。
  • 运用直觉I/O 多 多考虑线程/协程CPU 算力 要甩开 GIL 常考虑多进程 等(在 Python / CPython 前提下讨论)。

四. 进程与线程的对比

1. 关系
  • 线程依附在进程里,没有进程就没有线程。
  • 一个进程默认一条主线程 ,还可以再创建多个子线程
2. 区别与优缺点(Python 语境)
  • 全局变量 :进程之间不共享 ;线程之间共享 ,但要注意资源竞争 ,解决办法包括 join()互斥锁
  • 开销创建进程 > 创建线程
  • 单位 :进程是操作系统资源分配 的基本单位;线程是 CPU 调度的基本单位。
  • 独立执行线程不能脱离进程单独跑。
  • 稳定性(大纲表述)多进程 开发往往比单进程里只开多线程一些------某个进程出问题,不一定会拖垮其它进程。
  • 优缺点小结
    • 进程 :能用多核 、更接近真正并行 ;缺点是资源开销大
    • 线程开销小 ;在 Python 里受 GIL 等影响,不能指望用多线程把 CPU 密集型算满多核 ,更多是并发协作。

多进程,多线程与协程的区别**

五. 生成器与 yield

1. 什么是生成器
  • 按程序员定的规则 一次次生成数据,条件不成立就结束 ;数据不是一次性全部进内存 ,而是用一个再生成一个省内存
  • 创建方式 :① 生成器推导式 ② 含 yield 的函数(生成器函数)。
2. 生成器推导式
  • 与列表推导式类似,但用小括号 ()括号表示生成器 ,里面是生成规则 ,得到的是生成器对象,不是立刻算好的整表。
  • 取值:next(生成器) 每次取下一个;for 变量 in 生成器: 遍历剩余全部。
python 复制代码
my_generator = (i * 2 for i in range(5))
for value in my_generator:
    print(value)
3. yield 生成器函数
  • 只要在 def 里出现 yield ,就是生成器函数 ;调用函数得到生成器,不会一口气跑完函数体。
  • 执行到 yield暂停 ,把后面的值返回;下次 nextfor暂停处往下执行。
  • 生成器耗尽 后再 next,会抛出 StopIterationfor 循环会自动处理 这个结束;若用 while + next() ,一般要自己处理异常 或改用 for
  • return :结束生成器;具体与 StopIteration 的关系进阶再细讲,基础阶段掌握"结束迭代"即可。
python 复制代码
def mygenerater(n):
    for i in range(n):
        print('开始生成...')
        yield i
        print('完成一次...')

六. 协程

1. 与生成器的关系
  • Python 协程是从生成器 发展而来的:早期有 yield ,后来有 yield fromPython 3.5 起有 async / await底层仍与可暂停的执行有关
  • 一句话 :协程让 I/O 等待 的时候不闲着,去干别的事(协作式并发)。
  • 协程运行在线程里 ,由 asyncio 事件循环调度。
2. 协程三要素
  1. 函数前加 asyncasync def 定义协程函数;调用得到协程对象,不会立刻从头执行完)。
  2. 等待 处加 await (等待可 await 的对象,如 asyncio.sleep、另一个协程等;在等待点把控制权交回事件循环)。
  3. 顶层入口用 asyncio.run(...) 启动(最常用写法)。
3. 常见书写顺序
  1. async def 声明协程函数。
  2. 需要并发 多个协程时,用 asyncio.create_task(...) 包装成任务
  3. await 等待协程或任务结束。
  4. 程序入口 asyncio.run(main())
  • 注意 :对同一个协程函数连写两次 asyncio.run(...),两次之间是串行 的,时间相加;要并发 ,应在一个 async defcreate_task 多个任务await
python 复制代码
import asyncio

async def hello(name):
    print(f"开始: {name}")
    await asyncio.sleep(1)
    print(f"结束: {name}")

async def main():
    await hello("Alice")
    print("---")
    task1 = asyncio.create_task(hello("Bob"))
    task2 = asyncio.create_task(hello("Charlie"))
    await task1
    await task2

asyncio.run(main())
4. 协程、线程、进程
  • 进程 :资源隔离、可多核并行算力,开销大
  • 线程 :共享进程内内存,适合不少 I/O 场景;CPython 下 CPU 纯算往往不靠多线程撑满多核。
  • 协程单线程内 用事件循环调度大量 I/O 等待 任务,不开很多系统线程 也能高并发;不替代 多进程做重度 CPU 并行

总结

1. 互动

  • 创建线程的三个步骤是什么?

    import threadingthreading.Thread(target=任务函数名, ...)start()target 写函数名,不要写成 函数()

  • 线程执行顺序为什么可能每次不一样?

    :由 CPU / 操作系统调度 决定,没有固定先后顺序;多次运行顺序可能不同,属正常现象。

  • 主线程 默认会怎么对待子线程?守护线程的作用是什么?有哪两种常见设置方式?

    :默认 主线程会等所有子线程结束 ,进程才结束。守护线程:主线程退出后,子线程不再继续 。常见设置:Thread(..., daemon=True);或在 start() 之前 线程对象.daemon = True

  • 线程之间能不能共享全局变量?与进程有何不同?

    同进程内线程可以 共享全局变量;进程之间各自内存,不共享 全局变量。对全局变量重新赋值 往往要写 global

  • 线程同步 要解决什么问题?互斥锁 的大致步骤是什么?死锁是什么?

    线程同步 :约定先后访问共享资源,避免乱抢。互斥锁threading.Lock()acquire() / with lock: → 操作共享数据 → release()with 可自动释放)。死锁:线程互相等对方释放锁,程序卡死。

  • GILthreading.Lock 分别指什么?为什么有两层锁的说法?

    GIL :CPython 里解释器层的全局锁,同一时刻通常只有一条线程执行 Python 字节码Lock :保护你自己共享数据的显式 锁。为什么还要 Lock:一行源码对应多条字节码,线程仍可能在「读---改---写」中间被切换,业务数据仍要加锁。

  • 进程和线程在共享变量、开销、多核、稳定性方面怎样对比?

    :进程:不共享 全局变量、创建开销大 、更易 用满多核 做并行。线程:共享 进程内内存、开销较小 ;在 Python 里受 GIL 影响,CPU 密集型难靠多线程吃满多核,多偏向 I/O 并发。稳定性方面,大纲上常写多进程相对更「稳」一些。

  • 生成器 的两种创建方式?yieldreturn 在生成器函数里区别?

    :① 生成器推导式 (小括号) ② def 里含 yield 的生成器函数yield暂停并产出值 ,下次从暂停处继续;return结束生成器 (迭代结束与 StopIteration 的关系进阶再细讲)。

  • 协程三要素 是什么?asyncio.runcreate_task 分别什么时候用?

    :① async defawait ③ 入口常用 asyncio.run(...)asyncio.run :程序最外层 启动事件循环、跑顶层协程。create_task :在同一个 async def 里把多个协程包装成任务 以便 并发 ,再配合 await 等待结束。对同一协程连写两次 asyncio.run串行 、时间相加;要并发应在一个 协程里 create_task 多个再 await

相关推荐
SilentSamsara1 小时前
SQLAlchemy 2.x:异步 ORM 与数据库迁移 Alembic 完整指南
开发语言·数据库·python·sql·青少年编程·oracle·fastapi
27669582921 小时前
京东随机变速滑块拼图验证码识别(京东E卡)
java·服务器·前端·python·京东滑块·京东变速滑块·京东e卡绑卡
weixin_468466851 小时前
支持向量机新手实战指南
人工智能·python·算法·机器学习·支持向量机
程序大视界1 小时前
【Python系列课程】Python面向对象(下):封装、继承与多态
开发语言·python
夕小瑶1 小时前
Claude Code 保姆级上手教程(2026 版)
人工智能·python
天月风沙2 小时前
基于机器视觉的实验室器件仓储系统设计——内蒙古自治区国家级大创工程存档
开发语言·python
weixin_468466852 小时前
机器学习之决策树新手实战指南
人工智能·python·算法·决策树·机器学习·ai
Hesionberger2 小时前
巧用异或找出唯一数字(多解)
java·数据结构·python·算法·leetcode
hef2882 小时前
Python内置函数从入门到实战:list、open等核心用法全解析
python