🔥 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),解决多线程数据安全与协同问题,让并发编程更稳定、更安全!



