当前位置: 首页 > 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用法(创建、赋值、增删查改)详解
  • 11111111
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • Javascript 原型链
  • js继承的实现方法
  • JS实现简单的MVC模式开发小游戏
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • MD5加密原理解析及OC版原理实现
  • session共享问题解决方案
  • sublime配置文件
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 关于使用markdown的方法(引自CSDN教程)
  • 机器学习学习笔记一
  • 排序算法学习笔记
  • 浅谈web中前端模板引擎的使用
  • 探索 JS 中的模块化
  • $.ajax()
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (四)搭建容器云管理平台笔记—安装ETCD(不使用证书)
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • .net php 通信,flash与asp/php/asp.net通信的方法
  • .NET 表达式计算:Expression Evaluator
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .net 写了一个支持重试、熔断和超时策略的 HttpClient 实例池
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • .net和php怎么连接,php和apache之间如何连接
  • .NET连接MongoDB数据库实例教程
  • .Net中wcf服务生成及调用
  • @CacheInvalidate(name = “xxx“, key = “#results.![a+b]“,multi = true)是什么意思
  • [ HTML + CSS + Javascript ] 复盘尝试制作 2048 小游戏时遇到的问题
  • [ NOI 2001 ] 食物链
  • [.net 面向对象程序设计进阶] (19) 异步(Asynchronous) 使用异步创建快速响应和可伸缩性的应用程序...
  • [].shift.call( arguments ) 和 [].slice.call( arguments )
  • []Telit UC864E 拨号上网
  • [2021]Zookeeper getAcl命令未授权访问漏洞概述与解决
  • [Android]使用Retrofit进行网络请求