Python进程与线程入门:从区别到实操,避开90%的新手坑

张开发
2026/4/7 5:11:58 15 分钟阅读

分享文章

Python进程与线程入门:从区别到实操,避开90%的新手坑
刚接触多任务编程时大概率会被「进程」和「线程」这两个概念搞懵——它们都能实现“同时干多件事”但用法、区别、坑点却天差地别。结合自己从踩坑到熟练使用的经历今天就用最直白的语言把进程和线程讲透再结合全套实操代码按学习顺序整理帮你快速上手避开那些新手常踩的雷比如多进程必须加的保护、join方法的正确用法。一、先搞懂进程和线程到底是什么不管是进程还是线程核心目的都是「提高程序效率」让CPU不再“单打独斗”但它们的“分工”和“生存方式”完全不同。我们用一个生活化的比喻一眼看懂把计算机的CPU比作「一个厨房」程序要执行的任务比作「做饭」。1. 进程独立的“厨房”一个进程就相当于一个独立的厨房——有自己的空间、自己的厨具、自己的食材厨房之间互不干扰。比如你同时打开微信和Python编辑器这就是两个独立的进程微信进程负责接收消息、显示界面占用自己的内存和资源Python编辑器进程负责代码编辑、运行也占用独立的内存和资源。特点独立性强一个进程崩溃不会影响其他进程比如微信崩了Python编辑器还能正常用但创建、销毁的开销大占用资源多。2. 线程厨房⾥的“厨师”一个线程就相当于厨房⾥的一个厨师——共享厨房的空间、厨具和食材多个厨师可以同时干活比如一个切菜、一个炒菜。这些厨师就是同一个进程里的多个线程比如你用Python写一个多线程程序同时执行“下载文件”和“显示进度”这两个任务就是同一个进程里的两个线程它们共享程序的内存比如下载的进度数据可以互相访问。特点共享资源创建、销毁的开销小切换速度快但线程之间相互依赖一个线程崩溃可能导致整个进程崩溃比如一个厨师把锅砸了整个厨房都没法正常做饭。一句话总结核心区别「进程是资源分配的最小单位线程是CPU调度的最小单位」——通俗说进程管“资源”线程管“干活”一个进程里可以有多个线程多个线程共享进程的资源。二、Python实操多进程全套代码按学习顺序多进程适合「CPU密集型任务」比如视频渲染、大数据计算、复杂运算因为这类任务需要大量占用CPU多进程可以利用多核CPU同时干活。以下同样按学习顺序整理代码重点避开Windows系统的核心坑。02_多任务_多进程.py入门简单多进程实现importmultiprocessingimporttimedefsing():foriinrange(3):print(唱歌...)time.sleep(1)defdance():foriinrange(3):print(跳舞...)time.sleep(1)if__name____main__:# Windows系统必加否则报错# 创建两个进程p1multiprocessing.Process(targetsing)p2multiprocessing.Process(targetdance)# 启动进程p1.start()p2.start()# 多进程并发执行和多线程类似但进程是独立的资源空间03_多进程的参数传递.py核心给进程任务传参importmultiprocessing# 定义带参数的进程任务defeat(name,num):print(f{name}吃了{num}个桃子)if__name____main__:# 传参方式和多线程一致用args传递元组p1multiprocessing.Process(targeteat,args(小刚,6))p1.start()# 输出小刚吃了6个桃子进程启动后执行04_多进程的pid.py核心获取进程ID区分进程importmultiprocessingimportos# 用os模块获取进程IDdeftask():print(子进程PID进程ID,os.getpid())print(父进程PPID父进程ID,os.getppid())# 父进程就是主进程if__name____main__:print(主进程PID,os.getpid())pmultiprocessing.Process(targettask)p.start()# 每个进程都有唯一的PID可在任务管理器/top中定位进程05_演示多进程不能共享全局变量.py关键区别进程独立importmultiprocessing g_num100# 全局变量deftask1():globalg_num g_num200# 子进程p1修改全局变量print(task1p1进程,g_num)# 输出200deftask2():# 子进程p2读取全局变量print(task2p2进程,g_num)# 输出100不是200if__name____main__:p1multiprocessing.Process(targettask1)p2multiprocessing.Process(targettask2)p1.start()p2.start()# 结论多进程不能共享全局变量每个进程有自己独立的内存空间06_单进程可以共享全局变量.py对比单进程vs多进程g_num100# 全局变量deftask1():globalg_num g_num200# 修改全局变量deftask2():print(g_num)# 读取全局变量if__name____main__:# 单进程中两个函数直接调用共享全局变量task1()task2()# 输出200# 对比多进程单进程内的任务函数可以共享全局变量多进程不行07_演示主进程会默认等待所有的子进程结束.py进程生命周期importmultiprocessingimporttimedeftask():# 模拟子进程执行耗时time.sleep(2)print(子进程结束)if__name____main__:pmultiprocessing.Process(targettask)p.start()print(主进程结束)# 运行结果先打印“主进程结束”2秒后打印“子进程结束”# 结论和多线程一样主进程会默认等待所有子进程执行完毕程序才退出三、Python实操多线程全套代码按学习顺序多线程适合「IO密集型任务」比如下载、读取文件、网络请求因为这类任务大部分时间在等待线程切换快能提高效率。以下按学习顺序整理全套可直接运行代码覆盖从基础到线程安全的所有核心知识点。01_单任务.py基础铺垫无多线程顺序执行importtimedefsing():foriinrange(3):print(唱歌...)time.sleep(1)defdance():foriinrange(3):print(跳舞...)time.sleep(1)if__name____main__:# 单任务先唱歌 再跳舞顺序执行starttime.time()sing()dance()print(总耗时,time.time()-start)# 总耗时约6秒两个任务串行执行02_多任务多线程.py入门简单多线程实现importthreadingimporttimedefsing():foriinrange(3):print(唱歌中...)time.sleep(1)defdance():foriinrange(3):print(跳舞中...)time.sleep(1)if__name____main__:# 创建两个线程指定执行的任务t1threading.Thread(targetsing)t2threading.Thread(targetdance)# 启动线程t1.start()t2.start()print(主线程执行完毕)# 主线程先打印子线程继续执行体现多任务并发03_多线程参数传递.py核心给线程任务传参importthreadingimporttime# 定义带参数的线程任务defeat(name,num):print(f{name}吃了{num}个苹果)time.sleep(1)if__name____main__:# args 传递元组必须是元组即使只有一个参数也要加逗号t1threading.Thread(targeteat,args(小明,5))t2threading.Thread(targeteat,args(小红,3))t1.start()t2.start()# 输出小明吃了5个苹果 → 小红吃了3个苹果顺序可能互换04_多线程的编号.py核心获取线程ID区分线程importthreadingimporttimedeftask():# 获取当前线程的Python内部ID仅当前进程内唯一print(当前线程内部ID,threading.get_ident())# 获取当前线程的原生ID系统内核分配全局唯一可对应任务管理器print(当前线程原生ID,threading.get_native_id())time.sleep(1)if__name____main__:t1threading.Thread(targettask)t2threading.Thread(targettask)t1.start()t2.start()# 打印主线程ID区分主线程和子线程print(主线程内部ID,threading.get_ident())05_演示多线程可以共享全局变量.py关键特性importthreading# 定义全局变量g_num100deftask1():# 声明使用全局变量并修改它globalg_num g_num10print(task1 修改后全局变量,g_num)# 输出110deftask2():# 直接读取全局变量无需声明仅读取不修改print(task2 读取全局变量,g_num)# 输出110体现共享if__name____main__:t1threading.Thread(targettask1)t2threading.Thread(targettask2)t1.start()t2.start()# 结论多线程共享同一个进程的全局变量06_演示主线程会默认等待所有的子线程结束.py线程生命周期importthreadingimporttimedeftask():# 模拟子线程执行耗时time.sleep(2)print(子线程执行完毕)if__name____main__:tthreading.Thread(targettask)t.start()print(主线程执行完毕)# 运行结果先打印“主线程执行完毕”2秒后打印“子线程执行完毕”# 结论主线程会默认等待所有子线程执行完毕程序才会真正退出07_多线程是无序的.py核心特性执行顺序由系统调度importthreadingimporttimedeftask(name):# 模拟任务耗时让系统调度更明显time.sleep(1)print(f线程{name}运行)if__name____main__:# 循环创建5个线程foriinrange(5):tthreading.Thread(targettask,args(i,))t.start()# 每次运行线程输出顺序都不一样由系统调度决定# 结论多线程的执行顺序是无序的不可预测08_线程安全问题.py坑点多线程共享变量导致数据错误importthreading g_num0# 全局变量用于累加defadd():globalg_num# 循环100万次模拟大量操作放大线程安全问题foriinrange(1000000):g_num1# 看似简单的累加实际是“读取-计算-赋值”三步操作if__name____main__:t1threading.Thread(targetadd)t2threading.Thread(targetadd)t1.start()t2.start()# 等待两个子线程全部执行完毕t1.join()t2.join()print(最终累加结果,g_num)# 结果不是2000000而是小于2000000# 原因两个线程同时操作g_num出现“抢数据”的情况导致数据错误线程不安全09_join解决线程安全问题.py解决方案1串行执行规避冲突importthreading g_num0defadd():globalg_numforiinrange(1000000):g_num1if__name____main__:t1threading.Thread(targetadd)t2threading.Thread(targetadd)# 先启动t1等待t1执行完毕再启动t2t1.start()t1.join()# 阻塞主线程等待t1结束t2.start()t2.join()# 等待t2结束print(最终累加结果,g_num)# 正确输出2000000# 原理让两个线程串行执行避免同时操作全局变量解决线程安全问题# 缺点失去多线程并发的优势和单任务效率差不多10_利用互斥锁保证线程之间的安全.py推荐方案锁机制importthreading g_num0# 创建互斥锁相当于“排队叫号”一次只允许一个线程操作资源lockthreading.Lock()defadd():globalg_numforiinrange(1000000):lock.acquire()# 加锁进入临界区其他线程等待g_num1# 操作共享资源lock.release()# 释放锁退出临界区允许其他线程操作if__name____main__:t1threading.Thread(targetadd)t2threading.Thread(targetadd)t1.start()t2.start()t1.join()t2.join()print(正确累加结果,g_num)# 正确输出2000000# 原理互斥锁保证“同一时刻只有一个线程操作共享资源”既解决安全问题又保留并发优势11_死锁.py坑点锁的不当使用导致程序卡死importthreading# 创建两个互斥锁lockAthreading.Lock()lockBthreading.Lock()deftask1():lockA.acquire()# task1先拿到锁Aprint(task1 拿到锁A准备拿锁B)# 模拟任务耗时让task2有时间拿到锁Bthreading.sleep(1)lockB.acquire()# task1尝试拿锁B此时锁B已被task2拿到print(task1 拿到锁B开始执行)# 释放锁顺序无关但必须释放lockB.release()lockA.release()deftask2():lockB.acquire()# task2先拿到锁Bprint(task2 拿到锁B准备拿锁A)threading.sleep(1)lockA.acquire()# task2尝试拿锁A此时锁A已被task1拿到print(task2 拿到锁A开始执行)lockA.release()lockB.release()if__name____main__:t1threading.Thread(targettask1)t2threading.Thread(targettask2)t1.start()t2.start()# 运行结果程序卡住不动无法退出 → 死锁形成# 原因task1拿着锁A等锁Btask2拿着锁B等锁A互相等待无限阻塞四、新手必记进程和线程的核心区别表格对比对比维度进程Process线程Thread资源占用大独立占用内存、CPU等资源小共享进程的所有资源创建/销毁开销大速度慢小速度快独立性强一个崩溃不影响其他进程弱一个崩溃可能导致整个进程崩溃调度单位操作系统调度进程CPU调度线程更精细适用场景CPU密集型任务渲染、计算IO密集型任务下载、读取全局变量共享不共享每个进程独立内存共享同一进程内线程共享资源五、新手常踩的3个坑避坑指南坑1Windows多进程忘了加 ifname ‘main’报错表现出现RuntimeError提示“启动新进程前未完成引导阶段”。原因Windows启动子进程时会重新执行整个代码文件不加保护会无限创建进程。解决所有进程创建、start()的代码都放进「ifname ‘main’」里面参考多进程代码示例。坑2join()方法用错把多线程变成串行错误写法先start一个线程马上join再start另一个线程如09_join解决线程安全问题.py的写法。正确写法先start所有线程再依次join保证子线程并发执行如10_利用互斥锁保证线程之间的安全.py的写法。坑3误以为“多线程一定比单线程快”“多进程和多线程用法完全一样”真相1CPU密集型任务用多线程反而会因为线程切换消耗资源变慢此时应该用多进程。只有IO密集型任务多线程才能体现优势。真相2多进程不共享全局变量多线程共享全局变量多进程独立性强多线程依赖性强二者用法有本质区别。六、总结什么时候用进程什么时候用线程不用死记硬背记住两个核心场景如果你的任务是「等待型」下载、读文件、等接口响应→ 用多线程threading轻量、高效如果你的任务是「计算型」大量运算、渲染、数据分析→ 用多进程multiprocessing利用多核CPU提速明显。对于Python新手来说先掌握多线程的基本用法start、join、传参、锁机制再尝试多进程避开上面的坑就能轻松实现多任务编程。

更多文章