苦练Python第64天:从零掌握多线程,threading模块全面指南

作者:倔强青铜三 VIP.1 初学乍练日期:2025/10/14

前言

大家好,我是倔强青铜三。欢迎关注我,微信公众号:倔强青铜三。点赞、收藏、关注,一键三连!

欢迎继续 苦练Python第64天

今天咱们把“并发”这把瑞士军刀——threading 模块,从开箱到实战一次性讲透。全程只用 Python 自带标准库,代码复制即可运行!


一、为什么需要线程?

  • I/O 密集场景:爬虫、文件下载、日志采集,CPU 在等网络/磁盘,闲着也是闲着。
  • 共享内存:比多进程轻量,数据不用序列化来回拷贝。
  • GIL?别慌:I/O 密集时线程照样提速;CPU 密集请转投 multiprocessing

threading 常用 API 一览表

API作用关键入参返回值/副作用
threading.Thread(target, args=(), kwargs={}, name=None, daemon=None)创建线程对象target: 可调用对象;args/kwargs: 位置/关键字参数;name: 线程名;daemon: 是否为守护线程Thread 实例
Thread.start()启动线程,底层调用 run()若重复调用抛 RuntimeError
Thread.run()线程真正执行的逻辑;可被子类重写
Thread.join(timeout=None)阻塞等待线程结束timeout: 秒级浮点超时总是 None;超时后仍需用 is_alive() 判断是否存活
Thread.is_alive()线程是否存活True/False
Thread.name / Thread.ident / Thread.native_id线程名字/线程标识符/系统级线程 IDstr / int or None / int or None
Thread.daemon守护线程标志可读写布尔值设置前必须未启动
threading.current_thread()获取当前线程对象Thread 实例
threading.active_count()当前存活线程数量int
threading.enumerate()当前所有存活线程列表list[Thread]
threading.Lock()创建原始互斥锁Lock 实例
Lock.acquire(blocking=True, timeout=-1)获取锁blocking=False 非阻塞;timeout 秒级超时成功返回 True,否则 False
Lock.release()释放锁若未持有锁抛 RuntimeError
Lock.locked()查询锁状态True/False
threading.RLock()创建可重入锁RLock 实例;方法同 Lock
threading.Event()事件对象Event 实例
Event.set() / Event.clear() / Event.wait(timeout=None)置位/复位/等待事件timeout 秒级超时wait 返回 True(被 set)或 False(超时)
threading.Timer(interval, function, args=None, kwargs=None)延时线程interval: 延迟秒;function: 回调;args/kwargs: 参数Timer 实例,可 .cancel()
threading.local()线程局部数据容器local 实例,属性隔离

记住:带 timeout 的阻塞方法都可被 Ctrl+C 中断;
任何锁/事件/信号量都支持 with 上下文管理器,推荐优先使用!


二、30 秒启动你的第一个线程

1# demo_hello_thread.py
2import threading
3import time
4
5def say_hello(name, delay):
6    time.sleep(delay)
7    print(f"你好,{name},来自线程 {threading.current_thread().name}")
8
9# 创建线程
10t = threading.Thread(target=say_hello, args=("倔强青铜三", 2), name="青铜线程")
11t.start()          # 启动
12t.join()           # 等它跑完
13print("主线程结束")
14

运行效果:

1你好,倔强青铜三,来自线程 青铜线程
2主线程结束
3

三、批量任务:线程池手写版

场景:并发爬 3 个网页(用 sleep 模拟 I/O)。

1# demo_pool.py
2import threading
3import time
4
5links = ["https://a.com", "https://b.com", "https://c.com"]
6
7def crawl(url):
8    print(f"开始 {url}")
9    time.sleep(2)          # 模拟网络延迟
10    print(f"完成 {url}")
11
12threads = []
13for link in links:
14    t = threading.Thread(target=crawl, args=(link,))
15    t.start()
16    threads.append(t)
17
18for t in threads:
19    t.join()
20
21print("全部爬完!")
22

运行效果:

1开始 https://a.com
2开始 https://b.com
3开始 https://c.com
4完成 https://a.com
5完成 https://b.com
6完成 https://c.com
7全部爬完!
8

四、线程安全:Lock 互斥锁

卖票案例:100 张票,10 个窗口同时卖,不加锁会超卖。

1# demo_lock.py
2import threading
3
4tickets = 4
5lock = threading.Lock()
6
7def sell(window_id):
8    global tickets
9    while True:
10        with lock:              # 推荐用 with,自动 acquire/release
11            if tickets <= 0:
12                break
13            tickets -= 1
14            print(f"窗口{window_id} 卖出 1 张,剩余 {tickets}")
15        # 临界区外可快速做其他事
16    print(f"窗口{window_id} 下班~")
17
18threads = [threading.Thread(target=sell, args=(i+1,)) for i in range(4)]
19for t in threads:
20    t.start()
21for t in threads:
22    t.join()
23

运行效果:

1窗口1 卖出 1 张,剩余 3
2窗口1 卖出 1 张,剩余 2
3窗口1 卖出 1 张,剩余 1
4窗口1 卖出 1 张,剩余 0
5窗口1 下班~
6窗口2 下班~
7窗口3 下班~
8窗口4 下班~
9

五、线程通信:Event 红绿灯

主线程发信号让子线程起跑。

1# demo_event.py
2import threading
3import time
4
5start_event = threading.Event()
6
7def runner(name):
8    print(f"{name} 就位,等发令枪")
9    start_event.wait()          # 阻塞直到 set()
10    print(f"{name} 起跑!")
11
12for i in range(3):
13    threading.Thread(target=runner, args=(f"选手{i+1}",)).start()
14
15time.sleep(2)
16print("裁判:预备——跑!")
17start_event.set()               # 一枪令下
18

运行效果:

1选手1 就位,等发令枪
2选手2 就位,等发令枪
3选手3 就位,等发令枪
4裁判:预备——跑!
5选手1 起跑!
6选手2 起跑!
7选手3 起跑!
8

六、定时器:Timer 秒杀闹钟

延迟 3 秒响铃,可中途取消。

1# demo_timer.py
2import threading
3
4def ring():
5    print("⏰ 起床啦!!")
6
7alarm = threading.Timer(3, ring)
8alarm.start()
9print("3 秒后响铃,输入 c 取消")
10if input().strip() == "c":
11    alarm.cancel()
12    print("闹钟已取消")
13

七、线程局部变量:local 隔离数据

每个线程独享一份变量,互不打架。

1# demo_local.py
2import threading
3
4local = threading.local()
5
6def worker(num):
7    local.count = num          # 线程独有属性
8    for _ in range(3):
9        local.count += 1
10        print(f"{threading.current_thread().name} -> {local.count}")
11
12for i in range(2):
13    threading.Thread(target=worker, args=(i*10,), name=f"线程{i+1}").start()
14

运行效果:

1线程1 -> 1
2线程1 -> 2
3线程1 -> 3
4线程2 -> 11
5线程2 -> 12
6线程2 -> 13
7

八、完整实战:多线程文件下载器(模拟)

功能:并发“下载”多个文件,统计总耗时。

1# demo_downloader.py
2import threading
3import time
4import random
5
6urls = [f"https://file{i}.bin" for i in range(5)]
7results = {}
8lock = threading.Lock()
9
10def download(url):
11    print(f"开始 {url}")
12    sec = random.randint(1, 3)
13    time.sleep(sec)
14    with lock:
15        results[url] = f"{sec}s"
16    print(f"{url} 完成,耗时 {sec}s")
17
18start = time.time()
19threads = [threading.Thread(target=download, args=(u,)) for u in urls]
20for t in threads:
21    t.start()
22for t in threads:
23    t.join()
24
25print("全部下载完毕!")
26for url, spent in results.items():
27    print(url, "->", spent)
28print(f"总耗时 {time.time() - start:.2f}s")
29

运行效果:

1开始 https://file0.bin
2开始 https://file1.bin
3开始 https://file2.bin
4开始 https://file3.bin
5开始 https://file4.bin
6https://file1.bin 完成,耗时 1s
7https://file2.bin 完成,耗时 1s
8https://file4.bin 完成,耗时 2s
9https://file0.bin 完成,耗时 3s
10https://file3.bin 完成,耗时 3s
11全部下载完毕!
12https://file1.bin -> 1s
13https://file2.bin -> 1s
14https://file4.bin -> 2s
15https://file0.bin -> 3s
16https://file3.bin -> 3s
17总耗时 3.00s
18

九、with 上下文管理器 × threading:让锁像文件一样好写

还记得文件操作的 with open(...) as f: 吗?
threading 模块里的 Lock、RLock、Condition、Semaphore、BoundedSemaphore 全部支持 with 协议:
进入代码块自动 acquire(),退出时自动 release()——不会忘、不会漏、不会死锁!

9.1 原始锁 Lock 的两种写法对比

1# ❌ 传统写法:容易漏掉 release()
2lock = threading.Lock()
3lock.acquire()
4try:
5    # 临界区
6    global_num += 1
7finally:
8    lock.release()
9
10# ✅ with 写法:一行搞定,异常也不怕
11lock = threading.Lock()
12with lock:
13    global_num += 1
14

9.2 RLock 递归锁同样适用

1# demo_rlock_with.py
2import threading
3
4rlock = threading.RLock()
5
6def nested():
7    with rlock:          # 第一次获取
8        print("外层加锁")
9        with rlock:      # 同一线程可再次获取
10            print("内层重入,不会死锁")
11
12threading.Thread(target=nested).start()
13

9.3 Condition 条件变量 + with 经典范式

1# demo_condition_with.py
2import threading
3
4cv   = threading.Condition()
5flag = False
6
7def waiter():
8    with cv:                     # 自动 acquire
9        cv.wait_for(lambda: flag)  # 等待条件成立
10        print("waiter 收到通知!")
11
12def setter():
13    global flag
14    with cv:
15        flag = True
16        cv.notify_all()
17
18threading.Thread(target=waiter).start()
19threading.Thread(target=setter).start()
20

9.4 Semaphore 资源池限流

1# demo_sema_with.py
2import threading
3import time
4
5pool = threading.Semaphore(value=3)  # 并发 3 条
6
7def worker(i):
8    with pool:              # 获取令牌
9        print(f"任务 {i} 进入")
10        time.sleep(2)
11        print(f"任务 {i} 完成")
12
13for i in range(5):
14    threading.Thread(target=worker, args=(i,)).start()
15

9.5 自定义类也能支持 with

只要实现 __enter____exit__ 即可:

1class MyLock:
2    def __init__(self):
3        self._lock = threading.Lock()
4    def __enter__(self):
5        self._lock.acquire()
6        return self
7    def __exit__(self, exc_type, exc_val, exc_tb):
8        self._lock.release()
9
10with MyLock():
11    print("自定义锁也能 with!")
12

小结

武器用途备注
Thread创建线程始终记得 join
Lock/RLock临界区互斥推荐 with lock:
Event线程间通知set/clear/wait
Timer延迟执行可 cancel
local线程独享数据替代全局变量

最后感谢阅读!欢迎关注我,微信公众号:倔强青铜三
欢迎点赞、收藏、关注,一键三连!!


苦练Python第64天:从零掌握多线程,threading模块全面指南》 是转载文章,点击查看原文


相关推荐


局域网IP地址冲突排查与解决全指南:从诊断到预防
Bruce_xiaowei2025/10/12

局域网IP地址冲突排查与解决全指南:从诊断到预防 在局域网管理和维护中,IP地址冲突是一个常见但令人头疼的问题。当两台或多台设备被分配了相同的IP地址时,网络连接就会变得不稳定甚至中断。本文将详细介绍如何快速定位、解决并预防IP地址冲突问题。 IP地址冲突的识别与现象 典型症状表现: 设备网络连接时断时续频繁出现"网络电缆被拔出"提示Ping测试出现"一般故障"或"请求超时"特定网络服务无法访问 冲突根源分析: 手动配置IP地址时出现重复分配DHCP服务器范围设置不当网络中存在未经授权的DHCP


领码方案|微服务与SOA的世纪对话(5):未来已来——AI 驱动下的智能架构哲学
领码科技2025/10/11

📌 摘要 AI 已从工具升级为架构的“新大脑”,成为边界、治理、交付与演进的核心驱动力。本文按「方法论新生」模板,聚焦 AI 驱动下的智能架构哲学: 用智能双生体强化领域与基础设施模型用AI 增强 DDD 与契约,让边界自动进化用自驱动 Service Mesh,实现策略的智能演化用预测型 CI/CD,让发布成为预判与优化的闭环构建自演进反馈体系,让系统具备持续自优化能力 结合未来架构趋势与实践路径,给出端到端流程与行动清单,帮助组织完成从“自动化”到“智能化”的跃迁。 关键词:智能双


【LeetCode - 每日1题】水位上升的泳池中游泳问题
(时光煮雨)2025/10/9

🌈 个人主页:(时光煮雨) 🔥 高质量专栏:vulnhub靶机渗透测试 👈 希望得到您的订阅和支持~ 💡 创作高质量博文(平均质量分95+),分享更多关于网络安全、Python领域的优质内容!(希望得到您的关注~) 🌵目录🌵 难度 ⭐⭐⭐⭐⭐ 题目回顾 ✅解题思路分析 💖 概述 💓 核心思路 ✅ 代码分析 ✅ 复杂度分析 ✅ 测试用例验证 ✅


Flex 布局下文字省略不生效?原因其实很简单
序猿杂谈2025/10/8

概述 在开发中,我们常遇到这种布局:A、B、C 三段文本长度不固定,其中 A、C 占用空间较少,需要完整显示;而 B 是自适应区域,超出部分需省略显示。A 与 B 共享同一容器,右侧的 “Live” 按钮与该容器同级,容器应占据除 “Live” 之外的所有剩余空间。 HTML 结构如下: <div class="wrap"> <div class="infos"> <div>Soccer</div> <div class="self">Campeonato De Reser


Python 的内置函数 bin
IMPYLH2025/10/6

Python 内建函数列表 > Python 的内置函数 bin bin()函数是Python内置的一个简单但强大的工具,它能够将整数转换为以"0b"为前缀的二进制字符串表示形式。在计算机科学中,二进制是基础中的基础,理解二进制表示对于学习位运算、硬件接口编程、数据压缩等领域都至关重要。 bin 的函数原型如下所示: def bin(x:int): ''' 将一个整数转换为带前缀 `0b` 的二进制字符串 :param x: 一个整数 :return: x


MySQL Exporter采集的关键指标有哪些,如何解读这些指标?
IT橘子皮2025/10/5

MySQL Exporter 是 Prometheus 生态中用于监控 MySQL 数据库的核心工具,通过采集丰富的性能指标帮助用户分析数据库运行状态。以下是其关键指标分类及解读方法: 一、核心指标分类与解读 1. ​数据库可用性与基础状态​ ​**mysql_up**​ 值为 1表示 MySQL 实例正常运行,0表示不可用。用于快速判断数据库是否存活。 ​**mysql_global_status_uptime**​ 数据库已运行时间(秒)。若突然归零可能表示实例重启,需结合告警规则


从技术史看:Unix 从何而来
大聪明-PLUS2025/10/4

大家好!我是大聪明-PLUS! 如今,许多人认为“Unix”和“Linux”是同一个东西。但从 2024 年起,对于大多数我们认为是“Unix”和“Linux”的发行版来说,情况基本如此。 但 Unix 的历史悠久。如果你只了解我们今天所熟知的 Linux 系统,很难想象“Unix 早期的情况是什么样的”,因为自那时以来发生了太多变化。 ❯ 从原型到 Unix 让我们回顾一下 Unix 的起源。1969 年,贝尔实验室的研究员 Ken Thompson 正在尝试操作系统设计。 当时,贝


笔记本 光驱 的内部结构及用法: 应急系统启动 (恢复) 光盘 (DVD+R/RW)
穷人小水滴2025/10/3

光盘 (CD/DVD/BD) 基本上是一种被淘汰的古老存储技术了, 然而在特定领域, 光盘仍然具有明显的使用价值, 宝刀未老. 低成本 (特别是单张光盘很便宜), 防水防磁耐摔, 只读 (不可修改, 比如 DVD+R, BD-R), 读写设备与存储分离, 这些优点至今难以超越. 笔记本光驱 (轻薄小, 9 ~ 13mm 厚) (二手) 淘宝价约 30 元/个, 5.25 英寸 SATA 大光驱 (台式) (二手) 淘宝价约 20 元/个, 单张光盘 (全新) 只需 2 元. 本文介绍目前还能用的


unzip-6.0-21.el7.x86_64.rpm怎么安装?CentOS 7手动安装rpm包详细步骤
心灵宝贝2025/10/2

本文介绍了如何在 ​CentOS 7​ 系统上手动安装 unzip-6.0-21.el7.x86_64.rpm 这个 RPM 包,包括下载后如何用终端命令进行安装、常见问题的解决方法,以及如何验证是否安装成功 一、确保你有这个 rpm 文件 首先,你电脑上得有这个文件,比如它叫: ​unzip-6.0-21.el7.x86_64.rpm​ 安装包下载:https://pan.quark.cn/s/34cd3075c0fd 二、打开终端(命令行) 在 Linux 系统里(比如 Ce


开源多场景问答社区论坛Apache Answer本地部署并发布至公网使用
秦禹辰2025/10/2

本篇文章介绍如何在本地部署问答软件 Apache Answer,并结合 Cpolar 内网穿透发布至公网。 Answer 是一个高可扩展的开源知识型社区软件,对标国内外知乎等平台,可以免费使用 Answer 高效地搭建一个问答平台,任何组织与个人都可以免费使用 Answer 高效地搭建问答社区,用于产品技术问答、客户支持、用户交流等场景,让组织与用户之间、用户与用户之间更友好地交流、学习和成长。 在 Answer 构建的知识问答社区里,用户可以通过贡献高质量的内容、接受答案以及获得用户投票和

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0