【python高阶】-1- python工程和线程并发

一、项目工程守则

1.pdm

新建一个项目

命令行终端:pip install pdm

pdm init

版本号:x.y.z

  • x:兼容版本
  • y:新增功能
  • z:补丁版本

pdm add pytest requests (添加依赖)

pdm是协助管理我们的项目 

 

2. black

就是规范我们的代码风格的:

pdm add black

black

black test_api (./ 一次处理该目录下所有.py文件)

 

 

3.flake8

检查语法错误

pdm add flake8

新建配置文件 .flake8

里面写

这样就不会查所有的文件了

还要加上 extend-ignore = E501

 

4.isort

排序import

 

5.pytest

管理和执行测试用例

6.自定义命令

 将四个命令合并在一起

 

 7.项目管理

 src源代码 tests单元测试

二、错误和异常

python只有一种错误 语法错误 如果有语法错误的话整个文件都跑不起来 跑到半路停下来的都是异常

异常:执行前没有检查出错误,半路上遇到预期之外的情况

异常的引发方式:

python自动引发:

1 assert 1 == 2

2. 属性异常 print("beifan".beifan)

3. 导入异常 from sys import beifan

4. 索引异常 l = [ ]     print(l[100])

5. 键值对异常  d = {}   print(d["id"])

手动引发异常:

def add(a, b):if not isinstance(a, int):raise ValueError(f'{a=}, 不是整数,无法计算')if not isinstance(b, int):raise ValueError(f'{b=}, 不是整数,无法计算')return a + b

异常引发之后发生了什么

异常引发之后,Python 会进行下面的操作:

  1. 停止执行
  2. 收集信息
  3. 向上传播
    1. 不仅 add 函数停止执行
    2. main 函数也停止执行
    3. python 也停止执行

如何想要改变这个默认的处理流程,需对异常进行捕捉(打断传播链条)

捕捉异常可以:

  1. 打断异常的传播
  2. 自动执行预定义的代码

注意:

  1. 尽量只捕捉能处理的异常
  2. 不能处理的异常,应该继续传播
  3. 如果没有代码可以处理,就应该让 Python 停止运行

 

 

Pycharm 提供的 Debug (更方便)

console:执行任意的 Python 代码

debugger:控制代码执行进度、显示变量内容

步过:执行到下一行

步入:执行到下一行代码的内部

步出:执行完函数

恢复运行:运行到下一个断点处

Python 提供的 Debug(更核心) pdb:python debug
python -m pdb main.py
命令:

  • l: 查看代码
  • n: 步过
  • s: 步入
  • c: 恢复执行
  • b: 打断点
  • ! : 执行任意代码


三、python多线程 

(1)Python 多线程的两种实现方式

方式 1:继承 Thread 类

步骤

  1. 继承父类:定义一个类(如 T1 ),继承 threading.Thread ;
  2. 重写 run 方法:在类里实现 run 方法,写线程要执行的任务(如调用 task_1() );
  3. 调用 start 方法:创建线程对象后,用 start() 启动线程,自动执行 run 里的任务。
from threading import Thread  # 需导入 Thread 类# 1. 继承 Thread 类
class T1(Thread):  # 2. 重写 run 方法:定义线程任务def run(self):  print("线程任务开始")task_1()  # 假设 task_1 是要执行的函数print("线程任务结束")# 3. 实例化线程对象 + 启动线程
t_1 = T1()  
t_1.start()  # 启动线程,自动执行 run 方法# 可选:等待线程结束(避免主线程提前退出)
t_1.join()  

方式 2:实例化 Thread 对象

步骤
直接创建 Thread 对象,通过 target 参数指定线程要执行的任务(函数),再用 start() 启动。

from threading import Thread# 定义线程要执行的任务
def task_1():  print("线程任务开始")# 业务逻辑...print("线程任务结束")# 1. 实例化 Thread:用 target 指定任务函数
t_1 = Thread(target=task_1)  # 2. 调用 start 方法:启动线程,执行 target 对应的任务
t_1.start()  # 可选:等待线程结束
t_1.join()  

 


(2)线程的执行流程

不管用哪种方式创建线程,执行流程 都是固定的:

  1. 实例化:创建 Thread 对象(如 t_1 = T1() 或 t_1 = Thread(target=task_1) );
  2. 调用 start 方法:触发线程创建,系统会分配新的线程资源;
  3. 调用 run 方法:新线程启动后,自动执行 run 里的逻辑(方式 1 是重写的 run,方式 2 是 Thread 内置 run 会调用 target 函数 );
  4. 执行任务:最终执行 run 里的代码(或 target 函数 )。

 


(3)关键方法解析

1. start():启动线程
  • 作用:告诉系统 “创建新线程,执行任务”;
  • 注意:不能重复调用,一个线程对象只能 start() 一次。
2. run():线程的核心逻辑
  • 作用:定义线程要做的事;
  • 方式 1 中,需重写 run 来定制任务;
  • 方式 2 中,Thread 内置的 run 会自动调用 target 指定的函数,无需手动重写。
3. join():等待线程结束
  • 作用:主线程执行到 join() 时,会暂停自己,等子线程执行完再继续;
  • 场景:如果主线程需要用到子线程的结果,或避免子线程还没跑完,主线程就退出,就需要 join()

 

(4)两种方式的对比

方式优点缺点适用场景
继承 Thread 类可以重写多个方法,定制性强类继承会占用单继承名额需要复杂线程逻辑(如自定义 run )
实例化 Thread简单直观,无需创建类仅能通过 target 传任务简单任务,快速创建线程

 

(5)实际应用建议

  1. 简单任务选方式 2:直接传 target,代码更简洁;
  2. 复杂逻辑选方式 1:比如需要在 run 里加前置 / 后置操作(如初始化资源、清理数据 );
  3. 必用 join():如果主线程依赖子线程结果(如统计多线程下载的文件总数 ),一定要用 join() 等待,否则可能出现 “子线程还没跑完,主线程已经退出” 的问题。


四、线程池  

(1)为什么需要 “线程池”?

直接创建线程(如继承 Thread 类、实例化 Thread 对象 )有个问题:线程用完就销毁,频繁创建 / 销毁会浪费资源(比如测试 100 个任务,就要创建 100 个线程 )。

→ 线程池的作用

  • 预先创建一批线程(比如 3 个),放进 “池子” 里复用;
  • 有任务时,从池子里拿线程执行;任务结束后,线程不销毁,放回池子等下次复用;
  • 减少线程创建 / 销毁的开销,提升效率。

 


(2)线程池的核心逻辑

线程池的工作流程可以概括为 3 步:

  1. 创建线程池:根据需求设置最大线程数(max_workers),比如同时最多跑 3 个线程;
  2. 分配任务:有任务时,线程池自动选空闲线程执行任务;
  3. 复用线程:任务结束后,线程回到池子,等待下一个任务,不用销毁。

 


(3)线程池的使用步骤

步骤 1:导入模块

使用线程池需要导入 concurrent.futures 里的 ThreadPoolExecutor

from concurrent.futures import ThreadPoolExecutor
import time  # 用于统计耗时
步骤 2:定义任务(假设已有任务函数)

假设我们有 3 个任务函数,需要并行执行:

def task_1():time.sleep(1)  # 模拟任务耗时return "任务1结果"def task_2():time.sleep(1)return "任务2结果"def task_3():time.sleep(1)return "任务3结果"
步骤 3:创建线程池 + 提交任务(两种方式)
方式 1:用 pool.map 批量提交任务

pool.map 会自动遍历任务列表,把每个任务分配给线程池执行,返回结果列表。

# 记录开始时间
start_time = time.time()  # 1. 创建线程池:最多同时跑 3 个线程
pool = ThreadPoolExecutor(max_workers=3)  # 2. 提交任务:用 map + lambda 执行任务列表
#    lambda x: x() 表示“调用列表里的每个函数(task_1、task_2、task_3)”
res_list = pool.map(lambda x: x(), [task_1, task_2, task_3])  # 3. 关闭线程池:等待所有任务完成(也可以用 with 语法自动关闭)
pool.shutdown()  # 记录结束时间
end_time = time.time()  # 输出耗时
print(f'一共耗时:{end_time - start_time:.2f} 秒')  # 遍历结果
for res in res_list:print(res)

执行流程

  • 线程池创建 3 个线程;
  • pool.map 自动把 task_1task_2task_3 分配给线程执行;
  • 任务并行运行(因为 max_workers=3,3 个任务同时跑 );
  • 所有任务结束后,pool.shutdown() 关闭线程池;
  • 遍历 res_list 拿到每个任务的返回值。

 

方式 2:用 pool.submit 单个提交任务

Future 对象通常通过线程池 / 进程池的 submit() 方法创建

pool.submit 可以单个提交任务,返回 Future 对象,用于获取结果。

start_time = time.time()  pool = ThreadPoolExecutor(max_workers=3)  # 提交任务,返回 Future 对象
f_1 = pool.submit(task_1)  
f_2 = pool.submit(task_2)  
f_3 = pool.submit(task_3)  pool.shutdown()  # 等待任务完成end_time = time.time()  
print(f'一共耗时:{end_time - start_time:.2f} 秒')  # 通过 Future 对象的 result() 方法拿结果
print(f_1.result())  
print(f_2.result())  
print(f_3.result())  

执行流程

  • 逐个提交任务,线程池自动分配线程执行;
  • pool.shutdown() 等待所有任务完成;
  • 用 future.result() 获取每个任务的返回值。

(4)两种提交任务方式的对比

方式优点缺点适用场景
pool.map自动遍历任务列表,代码简洁必须等所有任务完成才返回结果任务数量明确,需要批量执行
pool.submit灵活控制任务提交,可单独拿结果代码稍繁琐任务数量不确定,或需要实时拿结果

(5)线程池的关键细节

1. max_workers 的设置
  • max_workers 是线程池里最多同时运行的线程数
  • 设置太小(比如 1 ),任务会串行执行,失去并行优势;
  • 设置太大(比如 100 ),会占用过多资源(CPU、内存 ),甚至拖慢程序;
  • 建议:根据任务类型(CPU 密集型 / IO 密集型 )调整,IO 密集型(如网络请求 )可以设大些(比如 10-20 ),CPU 密集型(如大量计算 )设小些(比如 4-8,和 CPU 核心数匹配 )。
2. pool.shutdown() 的作用
  • 关闭线程池,不再接受新任务;
  • 但会等待已提交的任务全部完成后,才真正关闭;
  • 也可以用 with 语法自动关闭(推荐 ):

    with ThreadPoolExecutor(max_workers=3) as pool:res_list = pool.map(lambda x: x(), [task_1, task_2, task_3])
    # 离开 with 块后,自动执行 pool.shutdown()
    
3. 异常处理

如果任务执行中抛出异常,pool.map 会在遍历 res_list 时抛出异常;pool.submit 则会在调用 future.result() 时抛出异常。需要用 try-except 捕获:

with ThreadPoolExecutor(max_workers=3) as pool:try:res_list = pool.map(lambda x: x(), [task_1, task_2, task_3])for res in res_list:print(res)except Exception as e:print(f"任务执行异常:{e}")

五、线程并发

 

一、变量通信 🌟

原理

多个线程可以共享同一个 “全局变量”,像小盒子一样📦 线程 A 往里塞数据,线程 B 能掏出数据~
但要注意!Python 里有个 GIL(全局解释器锁) 🚦 ,如果多个线程同时改变量,容易 “打架”(数据乱掉)!

代码示例(反面教材👉 线程打架现场)
import threading
import time# 共享小盒子📦
counter = 0  def increment():global counterfor _ in range(100000):# 非原子操作⚠️  读→改→写,线程会打架!counter += 1  threads = []
for _ in range(5):t = threading.Thread(target=increment)threads.append(t)t.start()for t in threads:t.join()# 理想是 500000,实际可能乱套!因为线程打架啦🥊
print("最终 counter 值:", counter)  
改进版(加锁保护🔒 让线程乖乖排队)
import threading
import timecounter = 0
# 可爱小锁🔒  同一时间只让一个线程动小盒子!
lock = threading.Lock()  def increment():global counterfor _ in range(100000):# 拿锁!其他线程乖乖等~with lock:  counter += 1  # 现在安全啦✨threads = []
for _ in range(5):t = threading.Thread(target=increment)threads.append(t)t.start()for t in threads:t.join()# 这次一定是 500000!完美~
print("最终 counter 值:", counter)  

二、队列通信 📮

原理

队列是 线程安全的 “传送带” 🛳️ 一个线程当 “生产者”(往传送带上放东西),另一个当 “消费者”(从传送带上拿东西)~ 不用自己加锁,超省心!

代码示例(生产者 - 消费者 🍞→🥪 )
import threading
import queue
import time# 可爱传送带📦
q = queue.Queue()  # 生产者:往传送带放面包🍞
def producer():for i in range(5):item = f"面包{i}"q.put(item)  # 放传送带上~print(f"生产者放: {item} 👉 传送带")time.sleep(0.5)# 消费者:从传送带拿面包做三明治🥪
def consumer():while True:item = q.get()  # 拿东西!没东西就等~print(f"消费者拿: {item} 👉 做三明治")q.task_done()  # 告诉传送带:我处理完啦✅if q.empty():  # 传送带空了,下班!break# 启动线程~
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)producer_thread.start()
consumer_thread.start()producer_thread.join()
q.join()  # 等传送带所有任务完成~

效果
生产者每隔 0.5 秒放面包,消费者马上拿走做三明治🥪 完美配合,不会乱~

三、锁通信 🔒

原理

锁是 “魔法小令牌” 🪄 线程拿到令牌才能动共享资源!其他线程只能乖乖等令牌~ 这样就不会打架啦!

代码示例(保护小盒子📦 )
import threading
import timecounter = 0
# 魔法令牌🔒
lock = threading.Lock()  def increment():global counterfor _ in range(100000):# 拿令牌!其他线程等~lock.acquire()  try:counter += 1  # 安全操作✨finally:lock.release()  # 还令牌!其他线程可以拿啦~threads = []
for _ in range(5):t = threading.Thread(target=increment)threads.append(t)t.start()for t in threads:t.join()# 结果一定正确~因为令牌守护着!
print("最终 counter 值:", counter)  
偷懒写法(with 自动管令牌🔄 )
import threading
import timecounter = 0
lock = threading.Lock()  def increment():global counterfor _ in range(100000):# 进入 with 自动拿令牌,退出自动还~超方便!with lock:  counter += 1  threads = []
for _ in range(5):t = threading.Thread(target=increment)threads.append(t)t.start()for t in threads:t.join()print("最终 counter 值:", counter)  

四、总结(超可爱版对照表🐾 )

方式可爱比喻优点缺点
变量共享小盒子📦简单直接容易打架(需加锁)
队列传送带📮线程安全、解耦省心要维护队列结构
魔法令牌🔒精准保护共享资源用不好会 “死锁”(慎用)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90938.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90938.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

YOLOv8 剪枝模型加载踩坑记:解决 YAML 覆盖剪枝结构的问题

1. 问题背景模型剪枝是实现模型轻量化、加速推理的关键步骤。然而,在 Ultralytics YOLOv8 的生态中,在成功剪枝后,进行微调(Fine-tuning)时会遇到一个令人困惑的现象:明明加载的是剪枝后的模型(…

js的学习1

1.数组 数组方法 push()数组尾部添加unshift()数组头部添加pop()数组尾部删除shift()数组头部删除splice(起始位置,删除几个元素,要替换的元素)删除指定的元素,改变了原数组,返回值是被删除的元素indexOf()第一次查到的索引&#…

LeetCode 2563.统计公平数对的数目

给你一个下标从 0 开始、长度为 n 的整数数组 nums &#xff0c;和两个整数 lower 和 upper &#xff0c;返回 公平数对的数目 。 如果 (i, j) 数对满足以下情况&#xff0c;则认为它是一个 公平数对 &#xff1a; 0 < i < j < n&#xff0c;且 lower < nums[i] n…

ZABBIX配置自动发现与自动注册,网易邮箱告警和钉钉告警

一、自动发现zabbix server 主动的去发现所有的客户端&#xff0c;然后将客户端的信息登记在服务端上。缺点是如果定义的网段中的主机数量多&#xff0c;zabbix server 登记耗时较久&#xff0c;且压力会较大。1、部署准备准备三台虚拟机192.168.80.151&#xff1b;192.168.80.…

QT(五)常用类

1. QString字符串类(掌握) QString是Qt的字符串类&#xff0c;与C的string相比&#xff0c;不再使用ASCII编码&#xff0c;QString使用的是Unicode编码。 QString中每个字符都是一个16位的QChar&#xff0c;而不是8位的char。 QString完全支持中文&#xff0c;但是由于不同的技…

EXCEL怎么提取表名

错误的方法&#xff1a;使用以下方法提取表名的时候&#xff0c;会存在1个问题&#xff0c;公式只在当前工作表生效&#xff0c;换工作表会出现表名覆盖的情况。RIGHT(CELL("filename"),LEN(CELL("filename"))-FIND("]",CELL("filename&quo…

springboot校园外卖配送系统

目 录 第一章 绪 论 1.1背景及意义 1.2国内外研究概况 1.3 研究的内容 第二章 关键技术的研究 2.1开发技术 2.2 Springboot框架介绍 2.3 Vue.js 主要功能 2.4 MVVM模式介绍 2.4 B/S体系工作原理 2.5 MySQL数据库 第三章 系统分析 3.1 系统设计目标 3.2 系统可行性…

【智慧物联网平台】安装部署教程——仙盟创梦IDE

一、部署前准备1. 环境要求基础环境&#xff1a;JDK 1.8、MySQL 5.7/8.0、Maven 3.6、Redis&#xff08;用于缓存&#xff09;、Node.js&#xff08;用于前端构建&#xff0c;可选&#xff09;。依赖服务&#xff1a;若需对接门禁、道闸等硬件设备&#xff0c;需确保设备网络可…

【安全漏洞】防范未然:如何有效关闭不必要的HTTP请求方法,保护你的Web应用

在构建和维护Web应用的过程中&#xff0c;安全问题总是我们最关心的话题之一。今天&#xff0c;我们要探讨的是一个经常被忽视的Web漏洞——未关闭或限制不必要的HTTP请求方法。 虽然我们在日常开发中主要使用 GET 和 POST 这两种请求方法&#xff0c;但像 PUT、DELETE、HEAD、…

嵌入式Linux裸机开发笔记8(IMX6ULL)主频和时钟配置实验(1)

引言在前几章实验中我们都没有涉及到 I.MX6U 的时钟和主频配置操作&#xff0c;全部使用的默认配置&#xff0c; 默认配置下 I.MX6U 工作频率为 396MHz。但是 I.MX6U 系列标准的工作频率为 528MHz&#xff0c;有些 型号甚至可以工作到 696MHz。本章学习 I.MX6U 的时钟系统&…

设计模式(四)创建型:生成器模式详解

设计模式&#xff08;四&#xff09;创建型&#xff1a;生成器模式详解生成器模式&#xff08;Builder Pattern&#xff09;是 GoF 23 种设计模式中的核心创建型模式之一&#xff0c;其核心价值在于将一个复杂对象的构建过程与其表示分离&#xff0c;使得同样的构建过程可以创建…

《Angular+Spring Boot:ERP前端采购销售库存协同架构解析》

基于Angular与Spring Boot构建的全栈ERP前端&#xff0c;绝非技术的简单叠加&#xff0c;而是通过深度融合两者特性&#xff0c;打造出兼具稳定性与灵活性的业务载体。Angular的组件化架构将复杂界面拆解为可复用的独立单元&#xff0c;依赖注入机制则让服务调用与数据流转条理…

Java 排序

文章目录排序插入排序分析希尔排序分析选择排序分析堆排序分析冒泡排序分析快速排序霍尔法分析挖坑法找基准前后指针法题目快排的优化三数取中法非递归实现快排归并排序分析非递归实现归并排序海量数据的排序非比较的排序计数排序分析基数排序桶排序排序 稳定的排序&#xff1…

日本IT就职面试|仪容礼仪篇分享建议

日系企業で好印象を与える「身だしなみ」と「面接マナー」ガイドこんにちは。 日系企業への就職・転職活動をされている方にとって、「第一印象」は合否を左右する大切なポイントですよね。実は、面接の評価は入室の瞬間から始まっていると言っても過言ではありません。 今回は…

英语听力口语词汇-8.美食类

1.crispy,crisp adj.酥脆的&#xff0c;易碎的 2.sweet adj.甜的 比如说chocolate is so sweet and delicious 3.chewy adj.难嚼的&#xff0c;难咽的 4.oatmeal n.燕麦粉 5.pickle n.泡菜 7.stir-fry v.炒菜 8.bacon n.咸肉&#xff0c;熏肉 9.yummy adj.美味可口的 1…

力扣7:整数反转

力扣7:整数反转题目思路代码题目 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−2^31, 2^31 − 1] &#xff0c;就返回 0。 思路 这道题我们可以分成两部分来做&#xff0c;一是完成反转二…

PWM信号控制电机

1&#xff1a;环境 STM32F103C8T6 KEIL5.38 2个电机 2个轮子 1个L298N STLINKV2 CH340 1个4位独立按键 杜邦线若干 2&#xff1a;代码 key.h #ifndef __KEY_H #define __KEY_H#include "stm32f10x.h"extern volatile uint8_t key_t ; extern volatile uint8_t …

开源赋能产业,生态共筑未来 | 开源科学计算与系统建模(openSCS)分论坛圆满举行

2025开放原子开源生态大会于7月23日-24日在北京国家会议中心召开。本届大会以“开源赋能产业&#xff0c;生态共筑未来”为主题&#xff0c;汇聚政、产、学、研、用、金、创、投等各领域开源力量&#xff0c;聚焦开源政策导向、生态发展趋势、开源产业实践&#xff0c;共探中国…

Android广播机制体系初识

Android广播机制体系大白话把Android的广播机制想象成小区里的“大喇叭”谁在喊话&#xff1f;任何App或系统都能当“大喇叭”&#xff0c;比如喊一嗓子“电量不足啦&#xff01;”&#xff08;这就是发送广播&#xff09;谁在听&#xff1f;其他App只要“竖起耳朵”&#xff0…

微信小程序点击输入框时,顶部导航栏被遮挡问题如何解决?

前言 不知道大家开发微信小程序的时候有没有遇到这么一个问题&#xff0c;就是在表单页面中&#xff0c;点击输入框后&#xff0c;输入框顶起会把顶部栏给遮挡住&#xff0c;如下图所示&#xff1a;遇到这种情况有没有解决的办法呢&#xff1f;能不能既将页面顶起&#xff0c;同…