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

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

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

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

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


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

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

1. 核心定义与关系

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

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

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

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

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

✅ 多线程 ≈ 多进程 性能

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

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

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

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

操作系统
进程1

资源独立
进程2

资源独立
线程1

共享进程资源
线程2

共享进程资源
线程3

共享进程资源
线程1

共享进程资源

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


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

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

1. 串行 vs 多线程 对比

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

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

2. GIL 与线程切换原理

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

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


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

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

1. 核心步骤

  1. 导入 threading 模块

  2. 定义线程执行的函数

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

  4. 调用 start() 启动线程

2. 完整实战代码

python 复制代码
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. 核心规则

  1. 自定义类继承 threading.Thread

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

  3. 不要重写 start() 方法

  4. 可重写 __init__ 传递自定义参数

2. 完整实战代码

python 复制代码
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 类 封装性强、可扩展、易维护 逻辑复杂、需要自定义属性/方法

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

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

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

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

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

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


六、下期预告

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

相关推荐
白杨尚青8 小时前
C++入门篇(十):string(上)——认识string:构造与三大遍历(一条龙讲透operator[]、迭代器、auto、范围for)
java·开发语言·c++·笔记·stl
Eddyp8 小时前
避开溢价!艾默生 TREX2 手操器省钱选型攻略
网络·其他·百度·微信·微信公众平台·新浪微博
FlightYe8 小时前
视界原理之2D视频(二):视频文件里有什么
android·linux·网络·c++·ffmpeg·音视频·aac
可乐鸡翅yeah_8 小时前
hls.js 切换多个视频源,新手开发常见踩坑
开发语言·前端·javascript·ios·ffmpeg·音视频·safari
Tanshu_API君8 小时前
API 聚合平台选型指南:从参数拆解到生产落地的十个评估维度
开发语言·api
Chen—LSN8 小时前
C语言——⽂件操作(1)
c语言·开发语言·c++·经验分享·笔记·算法·链表
狗凯之家源码网8 小时前
PHP 开源 IM 即时通讯系统效果实测与功能展示
开发语言·开源·php
虎虎(_ _)。゜zzZ9 小时前
关于字典中的解包问题
python
辛苦才能9 小时前
C++ map/set 深度解析:从关联式容器的本质到 operator[] 的三重身份
开发语言·c++
全栈弄潮儿²⁰²⁴9 小时前
Python实战第2期: 变量与数据类型
python·变量·数据类型·python实战