Python基础学习之认识线程
前言在写第一行多线程代码之前先搞清楚「线程」到底是什么比记住Thread的用法更重要。很多诡异的 bug——数据被莫名覆盖、程序退不出来、日志顺序乱成一团——根源都是对线程模型的理解有偏差而不是语法不会写。常见的误解有两个。一是把线程当成「更轻的函数调用」以为start()之后就一定按顺序执行二是把线程当成「能提速的黑盒」遇到慢就想开线程。前者忽略了线程之间的执行顺序完全不确定后者忽略了CPython 有 GIL多线程对计算密集任务是帮不上忙的。本文只讲概念与模型线程和进程的区别、线程的状态、主线程与守护线程、以及为什么必须有同步机制。代码示例以 CPython 3.8 及以上为基准只做演示不涉及具体业务。一、线程是什么从「执行流」说起一个正在运行的程序至少有一条执行流从入口开始一条条语句往下走。这条执行流就是主线程main thread。线程thread就是一条独立的执行流。给程序加一个线程等于让它同时走两条路一条路在等待网络返回另一条路可以继续读文件。操作系统负责在它们之间来回切换让每条路都感觉自己一直在跑。线程属于进程process。一个进程可以有很多线程它们共享这个进程的内存空间所以能直接读同一个对象打开的文件和套接字描述符环境变量、当前工作目录它们各自独占的只有调用栈函数调用到哪一层了寄存器状态当前算到哪了「共享内存」是线程最核心的特征它既带来了线程间通信的方便也带来了所有同步问题。对比项多进程多线程内存各自独立同一进程内共享数据共享方式需要序列化或共享内存直接读写同一个对象隔离性强一个崩了不牵连弱一个线程出致命错误可能带走整个进程创建开销大小CPython 能否真并行能默认不能受 GIL 限制二、并发不等于并行这两个词经常被混用但含义不同并发concurrency多件事交替推进。单核 CPU 也能做到并发靠快速切换。并行parallelism多件事同时推进需要多个物理核心。多线程在单核上就能实现并发只有在多核上、且不被 GIL 挡住时才谈得上并行。所以「用了线程」和「跑得更快」之间没有必然联系——如果任务是纯计算线程只是在几个核心中的一个上交替跑总时间不降反升。三、线程的四种状态理解状态有助于看懂线程为什么「不听话」状态含义新建new对象已创建还没start()就绪 / 运行runnable已启动等待调度或正在跑阻塞blocked在等锁、等 I/O、在sleep结束terminatedrun()正常返回或抛出未捕获异常Python 通过is_alive()暴露「是否还活着」这一个问题它把「就绪」和「阻塞」都算作 alive把「新建」和「结束」都算作 not alive。看下面这段代码# 适用于 Python 3.8import threadingdef work():passt threading.Thread(targetwork)print(t.is_alive()) # False还没 start()t.start()t.join()print(t.is_alive()) # False已经结束判断线程是否活着要配合join(timeout)使用join()永远返回None想知道它到底是「结束了」还是「超时了」就在join(timeout)之后再看一次is_alive()。四、主线程与守护线程主线程是程序启动时的那条执行流它不是守护线程。程序什么时候退出官方文档给的规则很明确默认情况下Python 程序会等到所有非守护线程都结束后才退出一旦只剩守护线程daemon thread整个程序就退出。守护线程常用于「跟着主程序一起生、一起死」的辅助任务比如后台心跳。但文档特别提醒守护线程在解释器关闭时是被突然掐断的它持有的文件句柄、数据库事务等可能来不及正确释放。所以要写关键数据请不要设成守护线程而是设成非守护再用一个信号例如threading.Event通知它主动收尾。# 适用于 Python 3.8import threadingimport timestop_event threading.Event()def background():# 用 Event 做协作式退出而不是等着被强杀while not stop_event.is_set():time.sleep(0.05)print(后台线程已优雅退出)t threading.Thread(targetbackground, daemonTrue)t.start()time.sleep(0.2)stop_event.set() # 通知它停t.join(timeout1) # 给它一点时间收尾print(主线程结束)daemon属性必须在start()之前设置start()之后再改会抛RuntimeError。旧的setDaemon()/isDaemon()写法自 Python 3.10 起已废弃直接用daemon属性即可。五、为什么必须同步共享内存的代价线程共享内存意味着两个线程可能同时操作同一个对象。由于解释器会在字节码之间切换线程切换间隔由sys.getswitchinterval()控制一段「看起来连续」的代码可能被打断。最经典的例子是累加# 适用于 Python 3.8import threadingtotal 0def add():global totalfor _ in range(50_000):total 1ts [threading.Thread(targetadd) for _ in range(3)]for t in ts:t.start()for t in ts:t.join()print(total) # 期望 150000实际通常偏小total 1不是原子操作它要先读total再加一再写回。两个线程如果都读到了同一个旧值就会有一个人的加一被覆盖这就是竞态条件race condition——程序不会报错只是结果悄悄错了。这类 bug 的危险之处就在于此它不一定每次复现测试环境跑得很正常上了线偶发。解决办法是给临界区加锁# 适用于 Python 3.8import threadingtotal 0lock threading.Lock()def add():global totalfor _ in range(50_000):with lock:total 1with lock:在进出时自动acquire()/release()即使临界区里抛异常也会释放比手写try/finally更不容易漏。六、一个可以观察现象的小例子下面这段代码用不同的切换间隔跑同一个多线程程序可以直观感受「线程调度是由解释器和操作系统共同决定的不保证顺序」。# 适用于 Python 3.8import sysimport threadingdef show(tag):for i in range(3):print(f{tag}-{i}, end )sys.setswitchinterval(0.000001) # 切换得非常频繁ts [threading.Thread(targetshow, args(tag,)) for tag in (A, B)]for t in ts:t.start()for t in ts:t.join()print() # 换行多跑几次会发现输出顺序并不稳定可能是A-0 B-0 A-1 B-1 ...也可能出现别的交错。这不是 bug而是并发的本质。永远不要依赖多线程的输出顺序也不要用「打印顺序对上了」来证明线程安全。注意sys.setswitchinterval()是全局设置会影响整个解释器里所有线程的调度行为生产代码里不要随手改。常见坑点1. 把线程当成顺序执行的函数调用❌t.start()之后立刻假设线程里的赋值已经完成。 ✅start()只是「已启动」结果是否就绪要用join()或Event来确认。2. 用守护线程做关键的写盘/写库❌Thread(targetsave, daemonTrue)主程序一结束就丢数据。 ✅ 关键任务设非守护并join()确需后台运行就用Event协作退出。3.start()之后再改daemon❌t.start(); t.daemon True—— 抛RuntimeError。 ✅ 在构造时写Thread(targetf, daemonTrue)。4. 以为「没报错就是对的」❌ 多线程累加看到结果大致对就放过。 ✅ 竞态是概率事件共享可变状态一律加锁或用queue.Queue。5. 依赖多线程的输出顺序❌ 用print的先后顺序判断哪个线程先跑。 ✅ 顺序不确定是正常现象需要次序就显式用锁或队列。6. 用is_alive()代替join()❌ 轮询while t.is_alive(): pass忙等把 CPU 占满。 ✅ 用t.join()阻塞等待或用join(timeout)设上限。7. 忘了join()也忘了with块❌ 手工Thread(target...)一批又不等待主线程提前退出。 ✅ 统一join()或换成ThreadPoolExecutor的with块自动收尾。8. 以为线程能加速纯计算❌ 用线程跑大量数学运算期待多核加速。 ✅ 受 GIL 限制CPU 密集任务请用multiprocessing/ProcessPoolExecutor。总结概念一句话结论线程进程内的一条独立执行流共享内存、独享调用栈并发 vs 并行并发是交替推进并行要多个核且不受 GIL 限制线程状态新建 → 就绪/运行 → 阻塞 → 结束is_alive()只回答「活着吗」主线程非守护程序会等所有非守护线程结束守护线程退出时被突然掐断不适合做关键写操作同步共享可变状态必须加锁或改用queue.QueueGILCPython 同一时刻只跑一个线程的字节码3.13 起有实验性 no-GIL 构建认识线程的关键是接受三件事执行顺序不确定、共享内存需要保护、受 GIL 限制的多线程只对 I/O 有效。把这三条内化之后后面学Lock、Condition、线程池都会顺理成章。

相关新闻

5G信令流程解析:从NAS/NGAP到PDU会话的排错实战指南

5G信令流程解析:从NAS/NGAP到PDU会话的排错实战指南

简介:《5G信令流程解析》是一份面向通信工程、网络运维及5G技术学习者的精简PDF文档,系统梳理5G网络从接入到数据传输的完整信令链路。资源仅1个PDF文件,压缩包大小105KB,便于快速查阅与移动学习。文档从信令流程概述入手&#xf…

2026/10/13 3:31:51 阅读更多 →
海明码编码与纠错原理详解:从校验位布局到SEC-DED工程实现

海明码编码与纠错原理详解:从校验位布局到SEC-DED工程实现

简介:海明码编码C实现资源包,面向计算机专业学生、通信工程学习者及需要了解纠错编码的开发者,帮助理解海明码在单比特错误检测与纠正中的原理,并掌握其代码实现方式。压缩包共28个文件,涵盖cpp源文件、h头文件、Visua…

2026/10/13 3:31:51 阅读更多 →
先别急着装防火墙,把计算机安全原理与实践手册读成作战地图

先别急着装防火墙,把计算机安全原理与实践手册读成作战地图

简介:《计算机安全原理:CompTIA Security及以后实验室手册(第二版)》是一部由McGraw-Hill出版、多位网络安全专家联合编写的英文原版实验室手册,适合网络安全初学者、Security备考人员及希望加强安全实操能力的从业者。…

2026/10/13 3:31:51 阅读更多 →

最新新闻

从零搭建ELK日志分析平台:架构设计、组件选型与实战调优

从零搭建ELK日志分析平台:架构设计、组件选型与实战调优

说实话,我在接手公司日志平台之前,排查故障靠的是“三件套”组合拳:登录服务器、grep关键字、tail -f盯屏幕。最开始那几年业务量不大,这招勉强够用,可一旦服务器数量超过两位数、日志格式五花八门,这套土办…

2026/10/13 12:58:10 阅读更多 →
数论基础实战:从紫书第10章到Kuangbin专题14的题型拆解

数论基础实战:从紫书第10章到Kuangbin专题14的题型拆解

如果你正卡在紫薯第10章数学,准备转战Kuangbin专题14数论基础,我给你一句忠告——书上的公式全会了,不代表你看到题目能动手。我当年啃完紫书第10章,带着一堆“看起来都能推导”的公式点开专题14,第一题就把我按在地上…

2026/10/13 12:58:10 阅读更多 →
Conda报错排查指南:从repodata失败到依赖冲突的完整解决

Conda报错排查指南:从repodata失败到依赖冲突的完整解决

两行红字一贴,小白当场懵,老手也得先深吸一口气。Collecting package metadata (current_repodata.json): failed和Solving environment: failed这对组合,几乎每个用过 Anaconda 的人都撞见过:刚装完环境准备conda install pandas…

2026/10/13 12:58:10 阅读更多 →
基于Halton序列的图像加密解密算法详解与Matlab实现

基于Halton序列的图像加密解密算法详解与Matlab实现

做图像加密解密这个方向的,八成都有过这种感受:教材里的经典算法讲得头头是道,真到自己动手复现,却总在“看起来像那么回事”和“实际效果经不起推敲”之间反复横跳。我之前在做基于Halton序列的图像加密解密项目时,最…

2026/10/13 12:58:10 阅读更多 →
Kettle 9.3 源码编译实战:从 GitHub 构建可部署的 PDI 生产环境

Kettle 9.3 源码编译实战:从 GitHub 构建可部署的 PDI 生产环境

简介:本资源为Kettle(Pentaho Data Integration)最新9.3版本的官方安装包百度网盘直链,面向数据工程师、ETL开发人员及大数据初学者,解决国内用户因网络限制难以获取国际开源ETL工具的问题。资源以1个14KB的docx文档形…

2026/10/13 12:58:10 阅读更多 →
Agent上线最后一公里:评测门禁、可观测性埋点与AgentOps闭环落地指南

Agent上线最后一公里:评测门禁、可观测性埋点与AgentOps闭环落地指南

1. 从"能跑"到"敢上线":Agent 交付的最后一公里到底卡在哪做过 Agent 项目的人大概都有过这种体验:本地 Demo 跑得飞起,工具调用、多轮对话、RAG 检索全都正常,演示给同事看的时候掌声一片。可真到了要往生产…

2026/10/13 12:57:09 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →