我是否需要锁定块来迭代共享列表?

时间:2014-03-15 10:08:56

标签: python

我知道python列表是线程安全的。 但是,假设我有一个共享列表

object_list = []

现在,线程会定期向此列表添加一个对象(数据结构),

object_list.add(aNewObject)

另一个线程在被调用时,会在for循环中访问此列表

for i in object_list:
    #do something with i

我需要这样的东西:

myLock.acquire()
either add an element or access the list with the for
myLock.release()

或者我还好吗? 我不在乎错过添加,但我担心如果在阅读主题在列表中间迭代时添加了一个新元素会发生什么......

1 个答案:

答案 0 :(得分:3)

这取决于您期望的保证。首先,python语言保证任何线程安全。

CPython实现顺便提交由于其GIL而提供了一些线程安全性,但是如果您计划在任何其他python实现下运行代码,那么您应该只是无论何时访问资源,都会使用锁定。

现在,对于CPython的具体考虑,如果您知道代码只会通过appendextend添加元素,那么最糟糕的情况是for循环将会迭代新元素。这可能是你想要的 1

>>> a = [0]
>>> for i, el in enumerate(a):
...     if i % 2 == 0:
...             a.append(i)
...     print(i, el)
... 
0 0
1 0
>>> a
[0, 0]
>>> for i, el in enumerate(a):
...     if i % 2 == 0:
...             a.append(i)
...     print(i, el)
... 
0 0
1 0
2 0
3 2
>>> a
[0, 0, 0, 2]

如果使用insert方法添加新项目,那么某些值可能会迭代一次以上,而其他一些值则在for期间不会迭代。例如:

>>> a = [1]
>>> for i,el in enumerate(a):
...     if i % 2 == 0:
...             a.insert(0, i)
...     print(i, el)
... 
(0, 1)
(1, 1)
>>> a
[0, 1]

此处1迭代两次,0中从未见过新插入的loop

同样从列表中删除元素可能会导致跳过某些元素的类似行为:

>>> for i, el in enumerate(a):
...     if i % 2 == 0:
...             a.remove(i)
...     print(i, el)
... 
(0, 0)
>>> a
[1]

正如您所看到的,在循环过程中从未见过1

这表示在迭代期间改变序列通常是你不想要的。在你的情况下,我建议使用锁。请注意,锁是context managers,因此我将循环写为:

with list_lock:
    for element in object_list:
        # do stuff

这简化了代码,因为您不必记住明确释放锁。插入元素时应该这样做(否则它没用):

with list_lock:
    object_list.append(new_element)

1 我没有使用单独的线程来简化示例并使其易于重现。在CPython中,在多个线程中完成操作不应该改变你看到的行为。在其他实现中不是这样。显然,使用多线程更难以重现行为。