Are lists thread-safe?
我注意到,通常建议使用多线程队列,而不是列表和
列表本身是线程安全的。在cpython中,gil防止并发访问它们,而其他实现则注意为它们的列表实现使用细粒度锁或同步数据类型。但是,尽管列表本身不能通过尝试并发访问而损坏,但列表的数据不受保护。例如:
1 | L[0] += 1 |
如果另一个线程执行相同的操作,则不能保证实际增加l[0],因为EDOCX1[0]不是原子操作。(非常少的操作实际上是原子操作,因为大多数操作都会导致调用任意的python代码。)您应该使用队列,因为如果只使用未受保护的列表,则可能会因为争用条件而获取或删除错误的项。
为了澄清托马斯出色的回答中的一点,应该提到
这是因为一旦我们开始写入数据,就不需要担心正在读取的数据将在同一位置。
这里有一个全面但非详尽的
我最近遇到了这样的情况,我需要在一个线程中连续地追加到一个列表,循环遍历这些项并检查该项是否已就绪,在我的情况下它是一个AsyncResult,只有当它已就绪时才将其从列表中删除。我找不到任何能清楚地证明我的问题的例子。下面是一个示例,演示如何在一个线程中连续地添加到列表中,并在另一个线程中连续地从同一列表中删除。有缺陷的版本很容易在较小的数字上运行,但要保持足够大的数字并运行几次,您就会看到错误。
有缺陷的版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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) |
出错时输出
1 2 3 4 5 6 7 8 9 | 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 |
使用锁的版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import threading import time count = 1000 l = [] r = threading.RLock() def add(): r.acquire() for i in range(count): l.append(i) time.sleep(0.0001) r.release() def remove(): r.acquire() for i in range(count): l.remove(i) time.sleep(0.0001) r.release() t1 = threading.Thread(target=add) t2 = threading.Thread(target=remove) t1.start() t2.start() t1.join() t2.join() print(l) |
产量
1 | [] # Empty list |
结论
正如前面的回答中所提到的,当从列表中附加或弹出元素的行为本身是线程安全的时,不线程安全的是当您在一个线程中附加并在另一个线程中弹出时。