Python 线程安全的对象缓存
我实现了一个Python的网络服务器。每当有一个HTTP请求进来,就会新建一个线程来处理这个请求。
我需要在内存中缓存一些对象,而且因为这是一个网络服务器,我希望这个缓存是线程安全的,也就是说多个线程同时访问时不会出问题。请问在Python中有没有标准的线程安全的对象缓存实现呢?我找到了一些资料:
http://freshmeat.net/projects/lrucache/
但是这个看起来并不是线程安全的。有没有人能推荐一个好的Python线程安全缓存的实现呢?
谢谢!
6 个回答
第一点。 GIL在这里并没有帮助,下面是一个关于“存根”的(不安全的)缓存的例子:
stubs = {}
def maybe_new_stub(host):
""" returns stub from cache and populates the stubs cache if new is created """
if host not in stubs:
stub = create_new_stub_for_host(host)
stubs[host] = stub
return stubs[host]
可能发生的情况是,线程1调用 maybe_new_stub('localhost')
,发现缓存中还没有这个键。然后我们切换到线程2,它也调用同样的 maybe_new_stub('localhost')
,结果也发现这个键不存在。因此,两个线程都调用了 create_new_stub_for_host
并把结果放入缓存中。
虽然缓存的映射本身是受到GIL保护的,所以我们不会因为同时访问而破坏它,但缓存的逻辑并没有受到保护,这样可能会导致我们创建两个或更多的存根,最后只留下一个,其余的都被丢弃了。
第二点。 根据程序的性质,你可能不想要一个全局缓存。这样的共享缓存会强制所有线程之间进行同步。为了提高性能,最好让线程尽可能独立。我认为我需要全局缓存,但你可能并不需要。
第三点。 你可以使用一个简单的锁。我从这个链接中获得了灵感,想出了以下代码,我认为这对我的目的来说是安全的。
import threading
stubs = {}
lock = threading.Lock()
def maybe_new_stub(host):
""" returns stub from cache and populates the stubs cache if new is created """
with lock:
if host not in stubs:
channel = grpc.insecure_channel('%s:6666' % host)
stub = cli_pb2_grpc.BrkStub(channel)
stubs[host] = stub
return stubs[host]
第四点。 最好使用现有的库。我还没有找到任何我愿意推荐的库。
每个请求都用一个线程通常不是个好主意。如果你的服务器在某些时候负载突然增加,会让服务器变得非常慢,甚至瘫痪。可以考虑使用线程池,这样在高峰期可以让线程数量增加到一个限制的大小,而在负载较轻的时候又能缩减到较少的线程。
在Python中,很多操作默认是线程安全的,所以标准字典在某些方面是可以放心使用的。这主要是因为有个叫做GIL的东西,它能帮助避免一些比较严重的线程问题。
这里有个列表,可能会对你有帮助: http://coreygoldberg.blogspot.com/2008/09/python-thread-synchronization-and.html
不过,这些操作的原子性只是意味着,如果两个线程同时访问一个字典,你不会得到完全不一致的状态。所以你不会得到损坏的值。但是,像大多数多线程编程一样,你不能依赖这些原子操作的具体顺序。
简单来说...
如果你的需求比较简单,并且不太在意写入缓存的顺序,那么你可以使用字典,并且可以放心你总能得到一致的、不损坏的值(只是可能会过时)。
如果你想确保在读写方面更一致一些,那么你可以看看Django的本地内存缓存:
http://code.djangoproject.com/browser/django/trunk/django/core/cache/backends/locmem.py
这个使用了读写锁来进行锁定。