
多个线程读取同一份配置,给字典套上 MappingProxyType,是不是就可以认为数据不会再变?这个代理拒绝通过自身修改映射,但它是原映射的动态视图。外部修改原字典,或者字典里的列表被修改,工作线程仍可能读到变化。
要建立稳定输入,先区分“接口不允许写”和“对象内容不再变化”。本文只用自有整数序列演示共享读,不涉及模型、外部服务或持久化副作用。
顶层复制和不可变值分别解决什么
假定输入为字符串键,值为只含整数的元组。先复制顶层字典,隔离后续对原映射的替换;再包成只读代理,防止消费者通过这个接口新增或替换键值。元组里的整数本身也不可变,才形成此处需要的稳定结构。
from types import MappingProxyType
from concurrent.futures import ThreadPoolExecutor
source = {"alpha":(1,2,3), "beta":(4,5)}
shared = MappingProxyType(dict(source))
source["alpha"] = (99,)
def calculate(key):
return key, sum(shared[key])
with ThreadPoolExecutor(max_workers=2) as pool:
print(list(pool.map(calculate, ["alpha","beta"])))
try:
shared["alpha"] = (0,)
except TypeError:
print("assignment rejected")
在本地 Python 3.9.6 下执行,返回 [('alpha',6),('beta',9)],通过代理赋值被拒绝。原字典里的 alpha 已替换为 (99,),但共享映射保持复制时的值,因此读到 6。
这里创建了两个工作线程,并未测量并行提速。求和负载很小,示例只验证数据读取边界。map 按输入顺序提供结果,不等于任务实际按这个顺序完成。
外层只读,不会递归冻结列表
换成列表,问题立刻不同:
nested = {"values":[1,2]}
view = MappingProxyType(nested)
nested["values"].append(3)
print(view["values"])
同一环境输出 [1,2,3]。甚至通过 view["values"].append(3) 也会访问同一个可变列表,因为被代理保护的是映射的赋值操作,不是每个值的内部状态。

若结构里有嵌套容器,需要把允许的结构转换为真正不可变的表示,或者明确复制与所有权规则。不能看见外面是元组就结束检查:元组里仍可能放着列表或字典。
深复制也不自动成为并发快照。若源数据在复制期间被其他线程修改,需要与写入方协作,确定复制过程读到的是哪一版。本文是在提交工作前构造共享对象,没有模拟并发写入。
每个任务的输入与失败都要能解释
calculate 只接收一个键,返回键与整数结果;它不修改共享映射。任务局部的中间结果不应写回这份共享输入,否则“只读数据”很快就变成了没有同步规则的共享状态。
对不存在的键,函数会抛 KeyError。另行通过单工作线程提交 missing 并调用 future.result(),确认异常传给调用方。这条验证不代表其他已提交任务被取消;一个任务失败后是否继续收集结果,需要单独定义。
关闭线程池时也要区分等待现有工作与取消尚未开始的工作。只读输入解决不了退出协议,更不会中断正在阻塞的任务。本篇没有模拟慢任务或取消时序,不提供相关延迟结论。
线程共享与进程传输不是同一条路径
线程可以引用同一个 Python 对象;跨进程任务通常需要传输或序列化。本文环境中尝试 pickle.dumps(shared) 抛出 TypeError,说明不能把当前这个代理对象直接当作已验证的进程池输入。
若改用进程池,可以传递可序列化的数据,在各进程初始化时重建只读结构;或者采用明确定义的共享内存布局。那会改变复制、序列化和生命周期成本,需要另外验证,不能从线程例子推断“只复制一次”或“零拷贝”。
稳定共享的范围要能写进契约
本例契约很窄:工作开始前构造完成;键是字符串,值是整数元组;消费者只读;任务输出独立;异常不伪装为正常结果。更换成文件句柄、数据库连接或包含缓存的对象后,这些条件不再自动成立。
可以保存共享输入的内容摘要与配置版本,帮助核对重跑时读的是哪一份数据。但摘要只表示某次内容,不能阻止仍有引用的可变对象在之后变化。
本次没有测序列化时间、内存节省或吞吐。它支持的结论是:只读代理的保护边界在映射接口,稳定输入还需要顶层复制、值的不可变性以及明确的写入所有权。
参考:Python MappingProxyType 动态视图、线程池与 Future。2026-10-21 回查并执行有限共享读与异常探针。


