【问题标题】:python threadsafe object cachepython线程安全对象缓存
【发布时间】:2008-10-17 19:05:20
【问题描述】:

我已经实现了一个 python 网络服务器。每个 http 请求都会产生一个新线程。 我需要在内存中缓存对象,并且由于它是一个网络服务器,我希望缓存是线程安全的。 python中是否有线程安全对象缓存的标准实现?我发现了以下

http://freshmeat.net/projects/lrucache/

这看起来不是线程安全的。谁能指出我在 python 中实现线程安全缓存的好方法?

谢谢!

【问题讨论】:

    标签: python multithreading caching


    【解决方案1】:

    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

    其中使用读/写锁进行锁定。

    【讨论】:

      【解决方案2】:

      每个请求的线程通常是一个坏主意。如果您的服务器遇到巨大的负载峰值,它将使机器瘫痪。考虑使用一个线程池,它可以在使用高峰期增长到有限的大小,而在负载较轻时缩小到较小的大小。

      【讨论】:

      • 问:“我该怎么做?” A:“你做 Y 的方式是错误的!”
      【解决方案3】:

      您可能想改用 memcached。它非常快、非常稳定、非常流行,有很好的 Python 库,并且可以让您在需要时扩展为分布式缓存:

      http://www.danga.com/memcached/

      【讨论】:

        【解决方案4】:

        第 1 点。 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 保护,因此我们不能通过并发访问来破坏它。然而,缓存的逻辑不受保护,因此我们最终可能会创建两个或更多存根,并将除一个之外的所有存根都丢弃。

        第 2 点。根据程序的性质,您可能不需要全局缓存。这种共享缓存会强制所有线程之间进行同步。出于性能原因,最好使线程尽可能独立。我相信我确实需要它,你可能实际上不需要。

        第 3 点。您可以使用简单的锁。我从https://codereview.stackexchange.com/questions/160277/implementing-a-thread-safe-lrucache 中汲取灵感并提出了以下内容,我认为可以安全地用于我的目的

        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]
        

        第 4 点。最好使用现有的库。我还没有找到任何我准备担保的东西。

        【讨论】:

          【解决方案5】:

          对于你想要的线程安全对象 threading.local:

          from threading import local
          
          safe = local()
          
          safe.cache = {}
          

          然后您可以使用线程安全在safe.cache 中放置和检索对象。

          【讨论】:

          • 您应该指出,线程本地缓存不会跨线程共享对象。
          【解决方案6】:

          我不确定这些答案是否符合您的要求。

          我有一个类似的问题,我正在使用一个名为 cachetools 的 lrucache 替代品,它允许您传递一个锁以使其更安全。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-04-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-11
            相关资源
            最近更新 更多