Python实现对象池设计模式

25

我需要一个对象池,但我不想自己实现它,我想找一个现成的并经过测试的Python库。

我发现了很多其他人寻找,但没有得到很多直接的答案,所以我把它带到了Stack Overflow上。

在我的情况下,我有大量的线程(使用threading模块),它们需要偶尔调用一个基于SOAP的远程服务器。它们可以各自建立与服务器的连接,但是建立套接字并完成认证过程很昂贵(它受到服务器的限制),因此我想共享一个连接池,在需要时创建更多。

如果要池化的项是工作子进程,我可能会选择multiprocessing.pool,但它们不是。如果它们是工作线程,我可能会选择这个实现,但它们不是。

如果它们是MySQL连接,我可能会选择pysqlpool,但它们不是。同样地,SQLAlchemy Pool也不适用。

如果有一个线程,使用可变数量的连接/对象,我会考虑这个实现,但我需要它是线程安全的。

我知道我可以相当快速地再次实现它,但鉴于有许多人在寻找它,我认为在Stack Overflow上提供一个规范答案会很好。

4个回答

29

从您的描述来看,我认为您需要的不是一组对象而是一组连接。为了简单的线程安全性,只需将可重复使用的连接存储在Queue.Queue实例(称为pool)中。当一个线程实例化一个连接封装对象时,该对象通过pool.get()获取其连接(如果当前没有可用连接,则自动排队等待,并在准备好连接时出列它)。当对象使用完其连接后,它通过pool.put将其放回池中。

除了Queue.Queue已经提供的功能之外,这里几乎没有普遍需要的通用功能,因此并不奇怪没有流行或广为人知的模块提供它 - 当一个模块总共只有大约6行功能代码时(例如调用用户提供的连接工厂以预先填充队列或即时填充到某个最大数量 - 通常不会增加很大的价值),要使一个模块得到广泛传播是很困难的。 厚重的胶水,过多地包装标准库模块的底层功能而没有实质性的增值,毕竟是一种架构上的缺陷;-)。


啊,对了,等待一下,如果池子里没有更多的东西了,那么这个列表就缺少了。我以为用列表而不是队列很聪明,但实际上太聪明了。 :) - Lennart Regebro
@Lennart,此外也没有线程安全的保证,具体是否会遇到问题取决于实现方式--使用Queue.Queue,则可以保证线程安全。 - Alex Martelli
Python已经内置了一个线程安全的队列?我不知道!是的,这将加速实现(我认为会很简短,但主要是花时间思考并发问题)。抱歉,我没有理解您关于“连接池”与“对象池”的区别。我说我想“共享连接池”,但是每个连接都包装在一个对象中,因此它也是一个对象池。然而,我试图做出的区别是连接对象是非活动的(不像multiprocessing.pool)。 - Oddthinking
2
@Oddthinking,没错,Python标准库中的Queue模块就是一个线程安全队列(基本的是LIFO,还有优先级和FIFO变体)。至于“池化什么”,我的意思是:尽可能池化轻包装或未包装的连接,因为建立连接是昂贵的部分;将当前未使用的连接包装在全新的包装对象中,为一个事务期间添加所有修剪应该是便宜且快速的比较,所以不需要池化包装器! - Alex Martelli

6
我遇到过类似的问题,必须说Queue.Queue非常好用,但是还有一个缺失的部分。下面的类可以帮助确保获取的对象被返回到池中。示例已包含在内。
我提供了两种使用此类的方式:使用关键字或封装对象使用析构函数。使用关键字更为推荐,但如果由于某些原因(最常见的是需要从多个队列获取多个对象)无法或不想使用它,则至少您有一个选择。如果您选择使用该方法,请注意标准免责声明中析构函数不会被调用。
希望这能帮助那些与OP和我遇到相同问题的人。
class qObj():
  _q = None
  o = None

  def __init__(self, dQ, autoGet = False):
      self._q = dQ

      if autoGet == True:
          self.o = self._q.get()

  def __enter__(self):
      if self.o == None:
          self.o = self._q.get()
          return self.o
      else:
          return self.o 

  def __exit__(self, type, value, traceback):
      if self.o != None:
          self._q.put(self.o)
          self.o = None

  def __del__(self):
      if self.o != None:
          self._q.put(self.o)
          self.o = None


if __name__ == "__main__":
  import Queue

  def testObj(Q):
      someObj = qObj(Q, True)

      print 'Inside func: {0}'.format(someObj.o)

  aQ = Queue.Queue()

  aQ.put("yam")

  with qObj(aQ) as obj:
      print "Inside with: {0}".format(obj)

  print 'Outside with: {0}'.format(aQ.get())

  aQ.put("sam")

  testObj(aQ)

  print 'Outside func: {0}'.format(aQ.get())

  '''
  Expected Output:
  Inside with: yam
  Outside with: yam
  Inside func: sam
  Outside func: sam
  '''

我认为唯一缺少的是一个__getattr__方法,这样你就可以将其视为封装对象的包装器。 - ThatAintWorking

0

对于简单的使用情况,这里是一个基于列表实现的对象池模式的示例:

源码:

https://sourcemaking.com/design_patterns/object_pool

https://sourcemaking.com/design_patterns/object_pool/python/1

"""
Offer a significant performance boost; it is most effective in
situations where the cost of initializing a class instance is high, the
rate of instantiation of a class is high, and the number of
instantiations in use at any one time is low.
"""


class ReusablePool:
    """
    Manage Reusable objects for use by Client objects.
    """

    def __init__(self, size):
        self._reusables = [Reusable() for _ in range(size)]

    def acquire(self):
        return self._reusables.pop()

    def release(self, reusable):
        self._reusables.append(reusable)


class Reusable:
    """
    Collaborate with other objects for a limited amount of time, then
    they are no longer needed for that collaboration.
    """

    pass


def main():
    reusable_pool = ReusablePool(10)
    reusable = reusable_pool.acquire()
    reusable_pool.release(reusable)


if __name__ == "__main__":
    main()

考虑在 ReusablePool 中使用 Python 队列而不是列表,因为它被宣传为线程安全。我不确定 Python 列表是否在所有实现中都是线程安全的。 - Oddthinking

0
你可以尝试我的一个开源的Python对象池。
Pond是一个高性能的Python对象池库,它具有更小的内存使用和更高的借用命中率。

https://github.com/T-baby/pondpond


这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - t.o.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接