列表是线程安全的吗?

我注意到经常建议使用多线程队列,而不是列表和.pop()。这是因为列表不是线程安全的,还是其他原因?

136762 次浏览

列表本身是线程安全的。在CPython中,GIL会防止对它们的并发访问,而其他实现则会对它们的列表实现使用细粒度锁或同步数据类型。然而,虽然列表自己不会因试图并发访问而损坏,但列表的数据不受保护。例如:

L[0] += 1

如果另一个线程做同样的事情,并不保证实际将L[0]增加1,因为+=不是一个原子操作。(Python中非常非常少的操作实际上是原子的,因为它们中的大多数会导致任意的Python代码被调用。)你应该使用队列,因为如果你只是使用一个不受保护的列表,你可能会因为竞争条件而获取或删除错误的项目

为了澄清Thomas的精彩回答中的一点,应该提到append() 线程安全。

这是因为我们不担心中的数据在中会在相同的位置。append()操作不读取数据,它只将数据写入列表。

list操作的下面是一个全面而不详尽的例子列表,以及它们是否线程安全。 希望得到关于obj in a_list语言构造在这里的答案

我最近遇到了这种情况,我需要在一个线程中连续地追加到一个列表,循环遍历项目并检查项目是否准备就绪,在我的情况下,这是一个AsyncResult,只有在它准备就绪时才从列表中删除它。 我找不到任何例子能清楚地说明我的问题 下面是一个示例,演示了在一个线程中不断地向列表中添加内容,并在另一个线程中不断地从同一列表中删除内容 有缺陷的版本很容易在较小的数字上运行,但保持数字足够大,并运行几次,您将看到错误

错误的版本

import threading
import time


# Change this number as you please, bigger numbers will get the error quickly
count = 1000
l = []


def add():
for i in range(count):
l.append(i)
time.sleep(0.0001)


def remove():
for i in range(count):
l.remove(i)
time.sleep(0.0001)




t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()


print(l)

错误时输出

Exception in thread Thread-63:
Traceback (most recent call last):
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-30-ecfbac1c776f>", line 13, in remove
l.remove(i)
ValueError: list.remove(x): x not in list

使用锁的版本

import threading
import time
count = 1000
l = []
lock = threading.RLock()
def add():
with lock:
for i in range(count):
l.append(i)
time.sleep(0.0001)


def remove():
with lock:
for i in range(count):
l.remove(i)
time.sleep(0.0001)




t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()


print(l)

输出

[] # Empty list

结论

正如在前面的回答中提到的,虽然从列表中追加或弹出元素本身是线程安全的,但当您追加一个线程并弹出另一个线程时,则不是线程安全的