Skip to main content
 首页 » 编程设计

多个线程共享的 Python 内存数组(想想进程空间中的 memcached)

2025年05月04日8sxdcgaq8080

所以我有一个多线程程序,简而言之,它下载网页,处理它们并存储结果。它用于处理网页的规则和其他内容存储在数据库中。最初,数据库完全崩溃了(处理每个网页需要向数据库发出 1-50 次请求)。第 1 步是在 memcached 中缓存此信息(如果域没有规则,它只会返回一个空字符串“”),这是一个巨大的改进,与为每个处理的项目敲打数据库 1-50 次相比。但我仍在研究 memcached,它增加了网络延迟(每个处理的项目有 1-50 次往返,加起来很快,即使在本地以太网上也是如此)。

所以我想将结果缓存在进程空间的数组中,基本上是在内存中复制 memcached。数据方面还不错,我将使用 Python 集来基本上复制键:值存储(足够简单)。

但事情是这样的:通常一堆线程会访问同一个站点并且需要相同的规则集,所以我想防止雷群问题(即 10 个线程都试图获取 example.com 的规则,如果不在本地缓存中,而不是在 memcached 中,将导致数据库受到攻击,虽然不是很严重,但有点)。

  1. 设置一个线程(“update_thread”)来更新内存数组,有一个工作队列,如果线程无法从本地缓存中获取域的规则,它会将域写入工作队列,并且休眠几秒钟然后再次尝试,休眠并再次尝试,直到本地内存缓存有一个空字符串“”或一组要使用的规则。线程“update_thread”读取工作队列并从 memcached 获取规则,如果不存在,则从数据库获取规则,并将它们写入 memcached 和本地缓存(如果没有规则,则在值中传播一个空字符串“”)。这样做的缺点是添加了一个线程;更多的 GIL 争用,轻微的延迟(我们必须等待 update_thread 运行,因为我们受 GIL 的支配)。再加上另一个线程和工作队列增加了复杂性。只有“update_thread”可以写入内存缓存数组,因此不需要锁定等。

  2. 我们使用锁来控制对内存中缓存数组的写访问。如果线程找不到规则集,它会尝试从 memcached 获取规则集,如果不在其中,它会访问数据库,一旦找到规则,它就会锁定内存数组并写入规则(或空字符串“”对于值)到内存缓存。缺点:我们可能仍然有雷群问题,但这可以通过为一个域写入一个特殊的值来抵消,例如“获取规则,等一下”,这会导致其他线程等待。

还有谁能想到其他解决方案,或者对我提出的两个解决方案发表评论吗?我怀疑我会选择第 2 种方法,因为锁定 + “获取规则,稍等片刻”似乎比添加线程和工作队列更简单。还是我错过了一些非常明显和简单的解决方案?

请您参考如下方法:

如果我理解正确的话,问题是多个线程倾向于同时从 memcached 检索相同的数据。您希望协调线程,以便一个线程检索数据,而其他线程等待,并在数据到达后共享数据。

为要缓存的对象制作一个包装器类。在开始通过网络检索值之前,在缓存中放置一个空包装器。如果另一个线程查找相同的数据,它将阻塞直到值到达。

这是包装器对象:

class PendingValue(object): 
    def __init__(self): 
        self._event = threading.Event() 
 
    def get(self): 
        self._event.wait() 
        return self._value 
 
    def set(self, value): 
        self._value = value 
        self._event.set() 

这是缓存:

class Cache(object): 
    def __init__(self): 
        self._dict = {} 
        self._lock = threading.Lock() 
 
    def __getitem__(self, key): 
        self._lock.acquire() 
        try: 
            pv = self._dict[key] 
            self._lock.release() 
            return pv.get() 
        except KeyError: #key not in cache 
            pv = PendingValue() 
            self._dict[key] = pv 
            self._lock.release() 
            value = retrieve_value_from_external_source() 
            pv.set(value) 
            return value