【问题标题】:How to wrap a Python iterator to make it thread safe?如何包装 Python 迭代器以使其线程安全?
【发布时间】:2012-11-07 13:09:16
【问题描述】:

因为有时它比围绕队列设计解决方案更实用,所以我想编写一个简单的包装器来使迭代器线程安全。到目前为止,我从thesetopics得到了灵感,想出了两个想法:

想法 1

class LockedIterator(object):
    def __init__(self, it):
        self._lock = threading.Lock()
        self._it = it.__iter__()
        if hasattr(self._it, 'close'):
            def close(self):
                with self._lock:
                    self._it.close()
            self.__setattr__('close', close)

    def __iter__(self):
        return self

    def next(self):
        with self._lock:
            return self._it.next()

我不喜欢它的是,如果我必须指定所有可能的方法,它会变得有点冗长 - 好吧,我不能 - 例如生成器的特殊情况。此外,我可能还有一些其他迭代器,它们现在已经隐藏了更具体的方法。

想法 2

class LockedIterator(object):
    def __init__(self, it):
        self._lock = threading.Lock()
        self._it = it.__iter__()

    def __getattr__(self, item):
        attr = getattr(self._it, item)
        if callable(attr):
            def hooked(*args, **kwargs):
                with self._lock:
                    return attr(*args, **kwargs)
            setattr(self, item, hooked)
            return hooked

这样更简洁,但它只能拦截调用,而不是例如直接更改属性。 (这些属性现在被隐藏以防止出现问题。)更重要的是,它使 Python 不再将我的对象识别为迭代器!

在不创建泄漏抽象的情况下,使所有迭代器(甚至更好:所有对象)都可以使用的最佳方法是什么?我不太担心不必要的锁定,但如果你能想出一个解决方案来规避这个问题,那就太好了!

【问题讨论】:

  • 被迭代的东西不能在获得每一位和下一个位之间发生变异吗?
  • @GP89 ​​我不确定你在问什么。创建锁定迭代器的全部意义在于,我可以在多个线程中安全地使用它,而无需使用队列。应该允许所有这些线程对该迭代器做任何事情,除了添加/删除属性,也许。
  • 线程锁本身就是上下文管理器,因此您可以将所有 try-except-finally 代码简化为 with self._lock:
  • @PaulMcGuire 谢谢!这有点清理它。
  • 我不知道何时需要跨线程共享迭代器,而不是共享并发集合。整个设计让我感到不安——试图避免使用适当的队列似乎已经是一个非常容易泄漏的抽象。

标签: python iterator thread-safety python-2.7


【解决方案1】:

首先,你知道the GIL吗?与简单的单线程版本相比,编写多线程 Python 的尝试通常会导致运行时间更慢。

您第一次尝试访问迭代器线程安全似乎很合理。您可以使用生成器使其更具可读性:

def locked_iter(it):
    it = iter(it)
    lock = threading.Lock()
    while True:
        try:
            with lock:
                value = next(it)
        except StopIteration:
            return
        yield value

【讨论】:

  • 我知道 Python 中线程的优点和局限性。就我而言,我必须处理网络延迟。您的解决方案看起来不错(您值得我的支持),但它仍然没有提供一种方法来保留特定类型的迭代器可能具有的任何其他属性。我决定不就我现在拥有的一个用例提出问题,特别是因为我希望解决方案更通用。
  • @tvdien 如果迭代器有其他属性,那么它就不是真正的迭代器,接口方面——在这种情况下,我们谈论的是代理任意对象。这在 Python 中是可能的,但它需要复杂的代码来处理特殊方法——使其工作的唯一方法是动态创建类。结果很慢,难以维护,而且很少值得付出努力。
  • @tvdien 我试图了解“网络延迟”的要求。 Python 线程如何帮助您解决网络延迟问题?使用标准的多线程习语线程安全队列有什么问题?
  • @tvdien __iter__ 的存在使对象成为 iterable,能够生成迭代器。 iterator 实际上产生值,因此只有next 方法。 yield next(it) 是不正确的,因为它会无意中捕获 StopIteration 在使用生成器时引发的问题。
  • @user4815162342,鉴于 Python 是鸭子类型并且基于 The Python Standard Library,我想您可能对“它是一个迭代器”的说法有点过于字面意思了。我想说,任何提供协议中描述的next__iter__ 方法的对象都符合迭代器的定义。
猜你喜欢
  • 1970-01-01
  • 2011-08-05
  • 2016-06-16
  • 1970-01-01
  • 2019-08-20
  • 1970-01-01
  • 2020-05-08
  • 2016-01-16
  • 2017-04-15
相关资源
最近更新 更多