4

我正在清理我在……不那么知识渊博时编写的一些 Python 代码。主要是因为对 Python 中的线程的不完全理解而产生的一些复杂性。我需要制作一个线程安全的项目列表,并且我想通过不可变列表来完成它,而不是通常的锁定方法。我知道不可变对象在线程方面非常特殊,因为围绕不完整状态更改的所有线程安全问题都会消失。

所以,我问:下面的代码是线程安全的吗?

class ImmutableList(object):
    def __init__(self):
        self._list = ()

    def __iter__(self):
        return self._list.__iter__()

    def append(self, x):
        self._list = self._list + tuple([x])

我认为是,因为每次都会构建一个新列表。如果在另一个线程迭代列表时更新了列表,则旧列表将继续用于剩余的迭代。这对我来说很好,但可能并不适合所有人。

另外,这是个好主意吗?我只想将此应用于列表大小较小且列表没有太大变化的少数情况(事件侦听器会浮现在脑海中)。

4

2 回答 2

15

首先,在 Python 编程语言的 CPython 参考实现中,附加到列表已经是线程安全的。换句话说,虽然语言规范不要求列表类是线程安全的,但无论如何都是这样。因此,除非您使用 Jython 或 IronPython 或其他类似的 Python 实现,否则您没问题。

其次,您还需要重载其他列表操作,例如__setitem__and__setslice__等。我假设您的实现可以处理这个问题。

最后,您的问题的答案是否定的:您的代码不是线程安全的。考虑以下情况:

  • 您的列表包含 (5, 6)
  • 线程 1 尝试追加 7,线程 2 尝试追加 8
  • 线程 1 构造另一个元组 (5, 6, 7),在此之前可以分配给 _list,有一个上下文切换
  • 线程 2 执行它的分配,所以列表现在是 (5, 6, 8)
  • 线程 1 重新获得对 CPU 的控制权并分配给 _list,覆盖之前的追加。列表现在是 (5, 6, 7) 并且 8 已经丢失。

这个故事的寓意是你应该使用锁定并避免聪明。

于 2009-04-13T02:35:47.597 回答
4

一个真正的不可变列表实现不允许底层列表结构改变,就像你在这里一样。正如@[Eli Courtwright] 所指出的,您的实现不是线程安全的。那是因为它并不是真正不可变的。为了实现不可变的实现,任何会更改列表的方法都将返回一个反映所需更改的新列表。

关于您的代码示例,这将要求您执行以下操作:

class ImmutableList(object):
  def __init__(self):
    self._list = ()

  def __iter__(self):
    return self._list.__iter__()

  def append(self, x):
    return self._list + tuple([x])
于 2009-04-13T02:57:27.373 回答