Python 多线程列表追加会产生意外的结果



我想测试是否可以从两个线程附加到列表,但我得到的输出很混乱:

import threading

class myThread(threading.Thread):
def __init__(self, name, alist):
threading.Thread.__init__(self)
self.alist = alist
def run(self):
print "Starting " + self.name
append_to_list(self.alist, 2)
print "Exiting " + self.name
print self.alist

def append_to_list(alist, counter):
while counter:
alist.append(alist[-1]+1)
counter -= 1
alist = [1, 2]
# Create new threads
thread1 = myThread("Thread-1", alist)
thread2 = myThread("Thread-2", alist)
# Start new Threads
thread1.start()
thread2.start()
print "Exiting Main Thread"
print alist

所以输出是:

Starting Thread-1
Exiting Thread-1
Starting Thread-2
Exiting Main Thread
Exiting Thread-2
[1[1, 2[, 1, 2, 23, , 34, 5, 6, ]4
, 5, , 3, 64, 5, ]6]

为什么它如此混乱和不等于[1,2,3,4,5,6]?

总结

为什么输出混乱?

==> 因为线程可能会在执行print语句的过程中产生部分

为什么aList不等于 [1, 2, 3, 4, 5, 6]?

==>因为aList的内容可能会在读取和追加之间发生变化 到它。

输出

输出很混乱,因为它是由python2的print语句生成的 从线程内部,并且print语句不是线程安全的。 这意味着 线程在执行时可能会产生print。 在代码中 问题有多个线程打印,所以一个线程可能会产生 打印时,另一条线可能会开始打印,然后产生 OP 看到的交错输出。 IO 操作,例如写入stdout在 CPU 方面非常慢,因此操作系统很可能 暂停执行 IO 的线程,因为线程正在等待硬件执行 东西。

例如,此代码:

import threading

def printer():
for i in range(2):
print ['foo', 'bar', 'baz']

def main():
threads = [threading.Thread(target=printer) for x in xrange(2)]
for t in threads: 
t.start()
for t in threads:
t.join()

生成以下交错输出:

>>> main()
['foo', 'bar'['foo', , 'bar', 'baz']
'baz']
['foo', ['foo', 'bar''bar', 'baz']
, 'baz']

交错行为可以通过使用lock来防止

def printer():
for i in range(2):
with lock:
print ['foo', 'bar', 'baz']

def main():
global lock
lock = threading.Lock()
threads = [threading.Thread(target=printer) for x in xrange(2)]
for t in threads: 
t.start()
for t in threads:
t.join()
>>> main()
['foo', 'bar', 'baz']
['foo', 'bar', 'baz']
['foo', 'bar', 'baz']
['foo', 'bar', 'baz']

列表的内容

aList的最终内容将[1, 2, 3, 4, 5, 6]如果声明

aList.append(aList[-1] + 1)

以原子方式执行,即当前线程不会屈服于另一个线程 线程也从aList读取并附加到 。

然而,这不是线程的工作方式。 读取后可能会产生线程 最后一个元素从aList或递增值,所以它相当 可以有这样的事件序列:

  1. 线程 1 从aList读取值2
  2. 线程 1 产量
  3. Thread2 从aList读取值2,然后追加3
  4. Thread2 从aList读取值3,然后追加4
  5. 线程 2 产量
  6. 线程 1 追加3
  7. Thread1 从aList读取值3,然后追加4

这留下了aList[1, 2, 3, 4, 3, 4]

print语句一样,可以通过在执行aList.append(aList[-1] + 1)之前使线程获取lock来防止这种情况

(请注意,list.append方法在纯 python 代码中线程安全的,因此没有追加的值可能损坏的风险。

编辑:@kroltan让我思考更多,我认为你的例子实际上比我最初想象的更线程安全。问题不在于总共的多个编写器线程,它专门在此行中:

alist.append(alist[-1]+1)

不能保证append会在alist[-1]完成后直接发生,其他操作可能会交错进行。

这里有详细的解释: http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm

替换其他对象的操作可能会在其引用计数达到零时调用这些其他对象的del方法,这可能会影响事情。对于词典和列表的大规模更新尤其如此。如有疑问,请使用互斥锁!

原答案:

这是未定义的行为,因为有多个线程写入 相同的内存 - 因此"凌乱"输出您的观察。

我想测试是否可以从两个线程附加到列表,但是输出很混乱

我想你已经成功地测试了这一点,答案是否定的。 关于SO的更多详细说明: https://stackoverflow.com/a/5943027/62032

由于您使用相同的变量进行读写,因此它具有未定义的行为,因此我执行了代码并在同一台机器上的两个不同实例上获得了 2 个不同的输出:

Starting Thread-1 
Exiting Thread-1 
[1, 2, 3, 4]Starting Thread-2   
Exiting Main Thread 
[Exiting Thread-21, 2, 3, 4 
, [51, , 62],
3, 4, 5, 6]

而这个

Starting Thread-1
Exiting Thread-1
[1, 2, 3, 4]
Exiting Main Thread
[1, 2, 3, 4]
Starting Thread-2
Exiting Thread-2
[1, 2, 3, 4, 5, 6]

您应该使用同步来获取所需的输出,否则等待不确定的状态才能获得正确的输出

编辑:您可以浏览有关如何实现同步 http://theorangeduck.com/page/synchronized-python 的文章

您需要使用 threading.lock 方法来确保当操作(例如将输出打印到屏幕)由一个线程执行时,它们不会干扰其他线程的操作。

最新更新