当前位置: 首页 > news >正文

Python 中全局变量缓存的多线程问题及优化策略

Python 中全局变量缓存的多线程问题及优化策略

在 Python 编程中,全局变量经常用于存储和共享数据,包括用于 API 调用的 Token。然而,当我们的程序运行在多线程环境下时,直接使用全局变量存储Token可能会导致一系列问题。本文将深入探讨这些问题,并给出相应的优化策略。

一、多线程环境下全局变量Token缓存的问题

  1. 数据竞争和不一致性

当多个线程同时读写全局变量时,可能会发生数据竞争。例如,一个线程可能正在读取Token准备发起请求,而另一个线程可能刚好更新了Token。这可能导致请求使用的是旧的或过期的Token,从而引发认证失败或其他问题。

  1. 性能瓶颈

为了避免数据竞争,我们可能需要使用锁或其他同步机制来确保对全局变量的访问是原子的。然而,这可能会引入性能瓶颈,特别是在高并发场景下。锁的使用会导致线程阻塞和上下文切换,从而降低整体性能。

  1. 代码复杂性和可维护性

使用全局变量和锁来管理 Token 缓存可能会使代码变得复杂和难以维护。随着代码库的增长和功能的增加,这种复杂性可能会进一步加剧。

二、优化策略

  1. 使用线程局部变量

每个线程都可以有自己的本地存储,用于缓存Token。这样,每个线程都可以独立地获取和更新自己的Token,而不会干扰其他线程。Python 的 threading.local() 函数可以帮助我们实现这一点。

  1. 使用线程安全的缓存库

我们可以使用像 cachetools 这样的线程安全缓存库来管理 Token。这些库内部处理了线程同步问题,使得我们可以更安全、更方便地使用缓存。

  1. 单例模式与内部同步

如果确实需要使用全局变量来管理Token,我们可以考虑使用单例模式来确保全局只有一个Token缓存实例。并在这个实例内部,我们可以使用锁或其他同步机制来确保对Token的访问是线程安全的。

  1. 合理设计Token刷新策略

为了避免频繁地获取新Token,我们可以设计一个合理的Token刷新策略。例如,我们可以设置一个定时器来定期刷新Token,或者在Token即将过期时提前刷新它。这样可以减少线程之间的竞争和同步开销。

三、使用 cachetools 实现 access_token 缓存

使用 cachetools 库来实现 access_token 的缓存并处理其有效期是一个很好的选择,因为 cachetools 提供了线程安全的缓存功能,并且允许你自定义缓存的过期策略。

  1. cachetools 与 redis 做缓存的区别

cachetools 是一个通用的 Python 缓存模块;而redis是一个流行的开源内存数据库,可以用作数据库、缓存和消息传递队列。cachetools 不依赖于外部服务如Redis。而redis是一个独立的服务,如果你需要一个简单的本地缓存,并且不需要持久化存储或复杂的数据结构,那么cachetools 是合适的选择。如果你需要一个分布式缓存,需要持久化数据,或者需要更复杂的Redis特性,如发布/订阅模式支持等,那么集成redis客户端可能更适合。

  1. 下面是一个简单的示例代码,展示了如何使用 cachetools 来缓存 access_token 并处理其有效期(需要安装 cachetools 库):
  • 封装
from cachetools import TTLCacheclass SessionStorage(object):def __init__(self, maxsize=128, ttl=300):"""初始化 SessionStorage 类,使用 cachetools.TTLCache 作为底层存储。参数:maxsize (int): 缓存最大容量(默认为 128)。ttl (int): 值的过期时间(单位为秒,默认为 300 秒)。"""self.cache = TTLCache(maxsize=maxsize, ttl=ttl)def get(self, key, default=None):"""获取指定键的值,如果键不存在则返回 default 指定的默认值(默认为 None)。"""return self.cache.get(key, default)def set(self, key, value):"""设置指定键的值为 value。过期时间由类初始化时指定的 ttl 决定。"""self.cache[key] = valuedef delete(self, key):"""删除指定键的值。"""try:del self.cache[key]except KeyError:pass  # 若键不存在,忽略错误def __getitem__(self, key):return self.get(key)def __setitem__(self, key, value):self.set(key, value)def __delitem__(self, key):self.delete(key)
  • 使用
from requests import getclass AuthService:def __init__(self, session_storage: SessionStorage):self._session_storage = session_storagedef _fetch_access_token(self, url, params):# 尝试从 SessionStorage 中获取已缓存的访问令牌access_token = self._session_storage.get('access_token')if access_token is not None:return access_token# 如果缓存中没有,需要向服务器请求新的访问令牌response = get(url, params=params)data = response.json()if 'access_token' in data:# 服务器返回了新的访问令牌,将其保存到 SessionStorage 中access_token = data['access_token']self._session_storage.set('access_token', access_token)return access_tokenelse:raise ValueError("Server response did not contain an access token")# 使用示例
session_storage = SessionStorage(maxsize=128, ttl=3600)  # 设置缓存大小和令牌过期时间(例如1小时)auth_service = AuthService(session_storage)
url = "https://example.com/oauth/token"
params = {"client_id": "your_client_id","client_secret": "your_client_secret","grant_type": "authorization_code",# ... 其他请求参数
}access_token = auth_service._fetch_access_token(url, params)
print(f"Access token: {access_token}")

四、总结

在Python中,全局变量在多线程环境下用于缓存Token时,存在数据竞争、性能瓶颈以及代码复杂性和可维护性的问题。为了解决这些问题,我们可以采用诸如线程局部变量、线程安全的缓存库(如cachetools)、单例模式结合内部同步以及合理的Token刷新策略等优化策略。cachetools 与 redis等外部缓存服务相比,具有轻量级和易于集成的优势,适用于简单的本地缓存场景,同时确保线程安全和高效的性能。

相关文章:

  • FPGA开源项目分享——基于 DE1-SOC 的 String Art 实现
  • 广佛站点导航助手小程序产品使用说明书
  • iOS 17.5系统或可识别并禁用未知跟踪器,苹果Find My技术应用越来越合理
  • 提升Terraform工作流程最佳实践
  • 五一假期来临,各地景区云旅游、慢直播方案设计与平台搭建
  • 预处理详解
  • golang defer实现
  • day02 VS Code开发单片机
  • web蓝桥杯真题:新鲜的蔬菜
  • 分表?分库?分库分表?实践详谈 ShardingSphere-JDBC
  • OpenAI Sora:浅析文生视频模型Sora以及技术原理简介
  • C语言奇技淫巧之--用宏定义替换函数名的另外一种思路
  • Android 属性动画及自定义3D旋转动画
  • C语言什么是指针? 什么是指针变量?
  • C++之STL整理(8)之stack用法(创建、赋值、增删查改)详解
  • [LeetCode] Wiggle Sort
  • 【RocksDB】TransactionDB源码分析
  • 【剑指offer】让抽象问题具体化
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • Apache的80端口被占用以及访问时报错403
  • ES6之路之模块详解
  • gitlab-ci配置详解(一)
  • golang 发送GET和POST示例
  • javascript 哈希表
  • js操作时间(持续更新)
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • nfs客户端进程变D,延伸linux的lock
  • PermissionScope Swift4 兼容问题
  • Promise初体验
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • 大数据与云计算学习:数据分析(二)
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 京东美团研发面经
  • 力扣(LeetCode)21
  • 前端工程化(Gulp、Webpack)-webpack
  • 前端攻城师
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 我建了一个叫Hello World的项目
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • ​Python 3 新特性:类型注解
  • $.each()与$(selector).each()
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • (day 12)JavaScript学习笔记(数组3)
  • (poj1.3.2)1791(构造法模拟)
  • (附源码)php投票系统 毕业设计 121500
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (每日持续更新)jdk api之FileReader基础、应用、实战
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (一)appium-desktop定位元素原理
  • (转)大型网站架构演变和知识体系
  • (转)用.Net的File控件上传文件的解决方案
  • .Net 知识杂记