I'm cleaning some of the Python code I wrote when I was...not as knowledgeable. Primarily I am killing some of the complexity that stemmed from an incomplete understanding of threading in Python. I need to make a list of items thread-safe, and I'd like to do it via immutable lists, instead of the usual locking approach. I know that immutable objects are very special with regard to threading because all the thread-safety issues surrounding incomplete state changes simply disappear.
我正在清理我写的一些Python代码,当时我不是那么知识渊博。主要是我正在消除由于对Python中的线程的不完全理解而产生的一些复杂性。我需要创建一个线程安全的项目列表,我想通过不可变列表,而不是通常的锁定方法。我知道不可变对象在线程方面非常特殊,因为围绕不完整状态更改的所有线程安全问题都会消失。
So, I ask: is the following code thread-safe?
所以,我问:以下代码是否是线程安全的?
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])
I think it is, because a new list is constructed each time. If the list is updated while another thread is iterating through it, the old list will continue to be used for the remainder of the iteration. This is fine by me, but may not be for everyone.
我认为是,因为每次都会构建一个新列表。如果在另一个线程迭代它时更新列表,则旧列表将继续用于迭代的剩余部分。这对我来说很好,但可能不适合所有人。
Also, is this a good idea? I only want to apply this to a few situations where the list size is small, and the lists aren't changed much (event listeners spring to mind).
这也是个好主意吗?我只想将它应用于列表大小较小的几种情况,并且列表不会发生太大变化(事件监听器会浮现在脑海中)。
2 个解决方案
#1
First of all, appending to a list is already thread-safe in the CPython reference implementation of the Python programming language. In other words, while the language specification doesn't require that the list class be thread-safe, it is anyway. So unless you're using Jython or IronPython or some other Python implementation like that, then you're fine.
首先,在Python编程语言的CPython参考实现中,附加到列表已经是线程安全的。换句话说,虽然语言规范不要求列表类是线程安全的,但无论如何。因此,除非你使用Jython或IronPython或其他类似的Python实现,否则你没事。
Second, you'd also need to overload the other list operations, such as __setitem__
and __setslice__
, etc. I'm assuming that your implementation handles this.
其次,您还需要重载其他列表操作,例如__setitem__和__setslice__等。我假设您的实现处理此问题。
Finally, the answer to your question is no: your code isn't thread safe. Consider the following situation:
最后,你的问题的答案是否定的:你的代码不是线程安全的。考虑以下情况:
- Your list contains (5, 6)
- Thread 1 tries to append 7, and Thread 2 tries to append 8
- Thread 1 constructs another tuple (5, 6, 7) and before that can be assigned to _list, there's a context switch
- Thread 2 performs its assignment, so the list is now (5, 6, 8)
- Thread 1 gets control of the CPU back and assigns to _list, overwriting the previous append. The list is now (5, 6, 7) and the 8 has been lost.
您的清单包含(5,6)
线程1尝试追加7,线程2尝试追加8
线程1构造另一个元组(5,6,7),在此之前可以分配给_list,有一个上下文切换
线程2执行其赋值,因此列表现在是(5,6,8)
线程1控制CPU返回并分配给_list,覆盖先前的附加。该列表现在(5,6,7),8已经丢失。
The moral of this story is that you should use locking and avoid cleverness.
这个故事的寓意是你应该使用锁定并避免聪明。
#2
A true immutable list implementation will not allow the underlying list structure to change, like you are here. As @[Eli Courtwright] pointed out, your implementation is not thread safe. That is because it is not really immutable. To make an immutable implementation, any methods that would have changed the list, would instead return a new list reflecting the desired change.
真正的不可变列表实现将不允许更改基础列表结构,就像您在这里一样。正如@ [Eli Courtwright]指出的那样,你的实现不是线程安全的。那是因为它不是真正不可改变的。为了实现不可变的实现,任何可能改变列表的方法都会返回一个反映所需更改的新列表。
With respect to your code example, this would require you to do something like this:
关于您的代码示例,这将要求您执行以下操作:
class ImmutableList(object):
def __init__(self):
self._list = ()
def __iter__(self):
return self._list.__iter__()
def append(self, x):
return self._list + tuple([x])
#1
First of all, appending to a list is already thread-safe in the CPython reference implementation of the Python programming language. In other words, while the language specification doesn't require that the list class be thread-safe, it is anyway. So unless you're using Jython or IronPython or some other Python implementation like that, then you're fine.
首先,在Python编程语言的CPython参考实现中,附加到列表已经是线程安全的。换句话说,虽然语言规范不要求列表类是线程安全的,但无论如何。因此,除非你使用Jython或IronPython或其他类似的Python实现,否则你没事。
Second, you'd also need to overload the other list operations, such as __setitem__
and __setslice__
, etc. I'm assuming that your implementation handles this.
其次,您还需要重载其他列表操作,例如__setitem__和__setslice__等。我假设您的实现处理此问题。
Finally, the answer to your question is no: your code isn't thread safe. Consider the following situation:
最后,你的问题的答案是否定的:你的代码不是线程安全的。考虑以下情况:
- Your list contains (5, 6)
- Thread 1 tries to append 7, and Thread 2 tries to append 8
- Thread 1 constructs another tuple (5, 6, 7) and before that can be assigned to _list, there's a context switch
- Thread 2 performs its assignment, so the list is now (5, 6, 8)
- Thread 1 gets control of the CPU back and assigns to _list, overwriting the previous append. The list is now (5, 6, 7) and the 8 has been lost.
您的清单包含(5,6)
线程1尝试追加7,线程2尝试追加8
线程1构造另一个元组(5,6,7),在此之前可以分配给_list,有一个上下文切换
线程2执行其赋值,因此列表现在是(5,6,8)
线程1控制CPU返回并分配给_list,覆盖先前的附加。该列表现在(5,6,7),8已经丢失。
The moral of this story is that you should use locking and avoid cleverness.
这个故事的寓意是你应该使用锁定并避免聪明。
#2
A true immutable list implementation will not allow the underlying list structure to change, like you are here. As @[Eli Courtwright] pointed out, your implementation is not thread safe. That is because it is not really immutable. To make an immutable implementation, any methods that would have changed the list, would instead return a new list reflecting the desired change.
真正的不可变列表实现将不允许更改基础列表结构,就像您在这里一样。正如@ [Eli Courtwright]指出的那样,你的实现不是线程安全的。那是因为它不是真正不可改变的。为了实现不可变的实现,任何可能改变列表的方法都会返回一个反映所需更改的新列表。
With respect to your code example, this would require you to do something like this:
关于您的代码示例,这将要求您执行以下操作:
class ImmutableList(object):
def __init__(self):
self._list = ()
def __iter__(self):
return self._list.__iter__()
def append(self, x):
return self._list + tuple([x])