欢迎光临
我们一直在努力

Python 多线程编程从入门到精通:原理+实战+最佳实践

🔥 Python 多线程编程从入门到精通:原理+实战+最佳实践

  • 一、先搞懂核心:进程 vs 线程 🧩
    • 1. 核心定义与关系
    • 2. 性能关键:IO 场景下的优势
    • 3. 进程与线程关系可视化
  • 二、多线程为什么快?爬虫场景秒懂 ⚡️
    • 1. 串行 vs 多线程 对比
    • 2. GIL 与线程切换原理
  • 三、实战第一种:Thread 类直接实例化 ✍️
    • 1. 核心步骤
    • 2. 完整实战代码
    • 3. 关键机制:守护线程 `setDaemon()`
    • 4. 关键机制:阻塞等待 `join()`
  • 四、实战第二种:继承 Thread 类 🎯
    • 1. 核心规则
    • 2. 完整实战代码
    • 3. 两种写法适用场景对比
  • 五、多线程核心知识点总结 📌
  • 六、下期预告

在 python 开发中,并发编程是提升程序效率、优化资源利用率的核心技能,而多线程作为并发编程的基础方案,几乎贯穿所有 IO 密集型场景。从爬虫提速到接口并发处理,多线程都能以轻量、高效的方式,让程序告别低效串行,真正实现“同时干活”。

本文将从零拆解 python 多线程核心原理,手把手实现两种主流编码方式,详解守护线程与 join() 阻塞机制,搭配实战代码与可视化逻辑,帮你彻底吃透多线程!


一、先搞懂核心:进程 vs 线程 🧩

很多初学者会混淆进程和线程,这是理解多线程的第一步,先理清两者的关系与定位:

1. 核心定义与关系

  • 进程:操作系统资源分配的最小单元,独立占有内存、文件句柄等系统资源,体积大、调度成本高。

  • 线程:操作系统调度执行的最小单元,依附于进程存在,共享进程资源,调度轻量、切换更快。

简单理解:进程是容器,线程是干活的工人,一个容器里可以有多个工人协同工作。

2. 性能关键:IO 场景下的优势

对于IO 密集型程序(网络请求、文件读写、数据库查询):

✅ 多线程 ≈ 多进程 性能

✅ 线程调度更轻量,系统开销远小于进程

✅ 遇到 IO 阻塞时,GIL 自动释放,线程无缝切换

这也是 python 多线程最适合的场景!

3. 进程与线程关系可视化

#mermaid-svg-K2B1nmDD23UuqL8J{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K2B1nmDD23UuqL8J .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K2B1nmDD23UuqL8J .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K2B1nmDD23UuqL8J .error-icon{fill:#552222;}#mermaid-svg-K2B1nmDD23UuqL8J .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K2B1nmDD23UuqL8J .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K2B1nmDD23UuqL8J .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K2B1nmDD23UuqL8J .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K2B1nmDD23UuqL8J .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K2B1nmDD23UuqL8J .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K2B1nmDD23UuqL8J .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K2B1nmDD23UuqL8J .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K2B1nmDD23UuqL8J .marker.cross{stroke:#333333;}#mermaid-svg-K2B1nmDD23UuqL8J svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K2B1nmDD23UuqL8J p{margin:0;}#mermaid-svg-K2B1nmDD23UuqL8J .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-K2B1nmDD23UuqL8J .cluster-label text{fill:#333;}#mermaid-svg-K2B1nmDD23UuqL8J .cluster-label span{color:#333;}#mermaid-svg-K2B1nmDD23UuqL8J .cluster-label span p{background-color:transparent;}#mermaid-svg-K2B1nmDD23UuqL8J .label text,#mermaid-svg-K2B1nmDD23UuqL8J span{fill:#333;color:#333;}#mermaid-svg-K2B1nmDD23UuqL8J .node rect,#mermaid-svg-K2B1nmDD23UuqL8J .node circle,#mermaid-svg-K2B1nmDD23UuqL8J .node ellipse,#mermaid-svg-K2B1nmDD23UuqL8J .node polygon,#mermaid-svg-K2B1nmDD23UuqL8J .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K2B1nmDD23UuqL8J .rough-node .label text,#mermaid-svg-K2B1nmDD23UuqL8J .node .label text,#mermaid-svg-K2B1nmDD23UuqL8J .image-shape .label,#mermaid-svg-K2B1nmDD23UuqL8J .icon-shape .label{text-anchor:middle;}#mermaid-svg-K2B1nmDD23UuqL8J .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K2B1nmDD23UuqL8J .rough-node .label,#mermaid-svg-K2B1nmDD23UuqL8J .node .label,#mermaid-svg-K2B1nmDD23UuqL8J .image-shape .label,#mermaid-svg-K2B1nmDD23UuqL8J .icon-shape .label{text-align:center;}#mermaid-svg-K2B1nmDD23UuqL8J .node.clickable{cursor:pointer;}#mermaid-svg-K2B1nmDD23UuqL8J .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K2B1nmDD23UuqL8J .arrowheadPath{fill:#333333;}#mermaid-svg-K2B1nmDD23UuqL8J .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K2B1nmDD23UuqL8J .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K2B1nmDD23UuqL8J .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K2B1nmDD23UuqL8J .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K2B1nmDD23UuqL8J .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K2B1nmDD23UuqL8J .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K2B1nmDD23UuqL8J .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K2B1nmDD23UuqL8J .cluster text{fill:#333;}#mermaid-svg-K2B1nmDD23UuqL8J .cluster span{color:#333;}#mermaid-svg-K2B1nmDD23UuqL8J div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K2B1nmDD23UuqL8J .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K2B1nmDD23UuqL8J rect.text{fill:none;stroke-width:0;}#mermaid-svg-K2B1nmDD23UuqL8J .icon-shape,#mermaid-svg-K2B1nmDD23UuqL8J .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K2B1nmDD23UuqL8J .icon-shape p,#mermaid-svg-K2B1nmDD23UuqL8J .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K2B1nmDD23UuqL8J .icon-shape .label rect,#mermaid-svg-K2B1nmDD23UuqL8J .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K2B1nmDD23UuqL8J .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K2B1nmDD23UuqL8J .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K2B1nmDD23UuqL8J :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

操作系统

进程1资源独立

进程2资源独立

线程1共享进程资源

线程2共享进程资源

线程3共享进程资源

线程1共享进程资源

图表说明:进程相互独立,线程隶属于进程并共享资源,操作系统直接调度线程执行。


二、多线程为什么快?爬虫场景秒懂 ⚡️

以最经典的爬虫场景举例,瞬间理解多线程的并发价值:

1. 串行 vs 多线程 对比

  • 串行执行:先爬列表页 → 等待网络返回 → 再爬详情页 → 等待网络返回,等待时间双倍消耗。

  • 多线程执行:线程1爬列表页、线程2爬详情页,IO 阻塞时线程切换,等待时间被充分利用,总耗时≈最长单线程耗时。

2. GIL 与线程切换原理

python 的 GIL(全局解释器锁)不会影响 IO 密集型场景的并发效率:

当线程执行网络请求(Socket IO) 时,GIL 自动释放,另一个线程立即接管执行,实现伪并发,效率远超串行。


三、实战第一种:Thread 类直接实例化 ✍️

这是 python 多线程最简写法,适合逻辑简单、快速实现的场景。

1. 核心步骤

  • 导入 threading 模块

  • 定义线程执行的函数

  • 实例化 Thread,指定 target(执行函数)和 args(参数)

  • 调用 start() 启动线程

  • 2. 完整实战代码

    import threading
    import time

    # 模拟爬取详情页
    def get_detail_html(url):
    print(f"✅ get_detail_html started:{url}")
    time.sleep(2) # 模拟网络IO等待
    print(f"✅ get_detail_html end:{url}")

    # 模拟爬取列表页URL
    def get_detail_url(url):
    print(f"✅ get_detail_url started:{url}")
    time.sleep(4) # 模拟网络IO等待
    print(f"✅ get_detail_url end:{url}")

    if __name__ == "__main__":
    start_time = time.time()

    # 实例化线程
    t1 = threading.Thread(target=get_detail_html, args=("https://xxx.com/1",))
    t2 = threading.Thread(target=get_detail_url, args=("https://xxx.com",))

    # 启动线程
    t1.start()
    t2.start()

    # 阻塞主线程,等待子线程执行完毕
    t1.join()
    t2.join()

    print(f"⏱ 总耗时:{time.time() start_time} s")

    3. 关键机制:守护线程 setDaemon()

    • 作用:将线程设为守护线程,主线程退出时,守护线程直接被终止,不等待执行完毕。

    • 用法:t.setDaemon(True)(必须在 start() 前设置)

    4. 关键机制:阻塞等待 join()

    • 作用:让主线程阻塞等待子线程执行完成,再继续后续逻辑。

    • 效果:总耗时 = 最长子线程耗时(示例中为4秒),证明并发执行。


    四、实战第二种:继承 Thread 类 🎯

    当业务逻辑复杂、需要封装多个方法时,继承 Thread 类是更优雅的方案,符合面向对象设计。

    1. 核心规则

  • 自定义类继承 threading.Thread

  • 必须重写 ** run() ** 方法(线程执行逻辑)

  • 不要重写 start() 方法

  • 可重写 __init__ 传递自定义参数

  • 2. 完整实战代码

    import threading
    import time

    # 继承Thread类,封装详情页爬取
    class GetDetailHtml(threading.Thread):
    def __init__(self, name, url):
    super().__init__(name=name) # 调用父类构造
    self.url = url

    def run(self):
    print(f"✅ {self.name} started:{self.url}")
    time.sleep(2)
    print(f"✅ {self.name} end:{self.url}")

    # 继承Thread类,封装列表页爬取
    class GetDetailUrl(threading.Thread):
    def __init__(self, name, url):
    super().__init__(name=name)
    self.url = url

    def run(self):
    print(f"✅ {self.name} started:{self.url}")
    time.sleep(4)
    print(f"✅ {self.name} end:{self.url}")

    if __name__ == "__main__":
    start_time = time.time()

    t1 = GetDetailHtml(name="detail_thread", url="https://xxx.com/1")
    t2 = GetDetailUrl(name="url_thread", url="https://xxx.com")

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print(f"⏱ 总耗时:{time.time() start_time} s")

    3. 两种写法适用场景对比

    写法优点适用场景
    直接实例化 Thread 代码简洁、快速编写 逻辑简单、动态创建线程、线程池
    继承 Thread 类 封装性强、可扩展、易维护 逻辑复杂、需要自定义属性/方法

    五、多线程核心知识点总结 📌

  • 线程是进程的子集,是 OS 调度最小单元,IO 场景下轻量高效。

  • 两种实现方式:直接实例化 / 继承 Thread 重写 run()。

  • 守护线程:setDaemon(True) → 主线程退出,守护线程终止。

  • 阻塞等待:join() → 主线程等待子线程完成,保证逻辑完整。

  • IO 密集型优先用多线程,CPU 密集型推荐多进程。


  • 六、下期预告

    下一篇将深入讲解线程间通信(Queue、Event、Lock),解决多线程数据安全与协同问题,让并发编程更稳定、更安全!

    Python 多线程编程从入门到精通:原理+实战+最佳实践

    赞(0)
    未经允许不得转载:171主机测评 » Python 多线程编程从入门到精通:原理+实战+最佳实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址