@lru_cache线程安全靠底层C层互斥逻辑实现,非用户手动加锁;多线程并发调用时可能重复执行未缓存函数,但不会崩溃或数据错乱。

@lru_cache 的线程安全机制靠什么实现?
它内部用的是带锁的字典(dict)加原子操作,不是靠用户加 threading.Lock。所有缓存读写都包裹在底层 C 层的互斥逻辑里,对 Python 层完全透明。
这意味着:多个线程同时调用同一个被 @lru_cache 装饰的函数,不会出现键冲突、缓存覆盖或 KeyError;但要注意——如果两个线程几乎同时发现某个参数没缓存(miss),它们都可能各自执行一次原函数,结果后写入的会覆盖先写入的(最终缓存里只留一个值)。这不是 bug,是设计使然。
并发下缓存命中率低?先看是不是参数哈希问题
常见现象:多线程跑同一组参数,cache_info().hits 却很低,甚至全为 misses。大概率是参数含不可哈希类型,比如:
-
list、dict、自定义类实例(未实现__hash__)直接传入,会触发TypeError: unhashable type - 用了关键字参数但顺序不固定,如
f(a=1, b=2)和f(b=2, a=1)被视为不同调用(参数名参与哈希)
解决办法:把 list 改成 tuple,dict 改成 frozenset(items()),或用 functools._make_key 自定义键生成逻辑(注意该函数非公开,仅作临时调试用)。
maxsize=1 在并发中真能当“单例工厂”用吗?
可以,但有前提:必须包装成无参函数,且构造逻辑本身是线程安全的。例如:
@lru_cache(maxsize=1)
def get_db_connection():
return Database.connect(host="localhost")
这样首次调用会初始化连接,后续所有线程都复用同一个实例。但注意三点:
- 不能传参,否则
maxsize=1失效(不同参数产生不同缓存项) - 如果
Database.connect()内部不是线程安全的(比如用了全局状态),那缓存住的这个实例仍可能引发并发问题 - 无法自动感知连接断开,需配合
cache_clear()手动重置
为什么有时加了 @lru_cache 反而更慢?
不是缓存本身慢,而是你用错了场景。典型情况包括:
- 函数本身执行极快(比如
abs(x)),缓存哈希+字典查找开销反而超过计算开销 -
maxsize设得过大(尤其是None),导致哈希表膨胀、内存抖动,GC 压力上升 - 参数含大对象(如长字符串、大型
tuple),每次哈希计算成本高,且缓存条目占内存多 - 启用了
typed=True但参数类型混杂(如频繁穿插int和float),导致本可命中的变成miss
真实压测时,务必对比 func.cache_info() 中的 hits 和 misses 比值——低于 0.3 就该怀疑缓存是否真起作用了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











