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]

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

原文由 Alexey 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 891
2 个回答

编辑:@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

原文由 tinkertime 发布,翻译遵循 CC BY-SA 3.0 许可协议

概括

为什么输出乱七八糟?

==> 因为线程可能会通过执行 print 语句而产生部分结果

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

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

输出

输出是混乱的,因为它是由 python2 的 print 线程内的语句生成的,而 print 语句不是 线程安全 的。这意味着线程可能会在执行 print 时产生。在问题的代码中有多个线程打印,因此一个线程可能在打印时产生,另一个线程可能开始打印然后产生,从而产生 OP 看到的交错输出。诸如写入 stdout 类的 IO 操作在 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从—读取值 2 aList
  2. 线程 1 产量
  3. Thread2 从 — 读取值 2 aList ,然后追加 3
  4. Thread2 从 — 读取值 3 aList ,然后追加 4
  5. 线程 2 产量
  6. Thread1 追加 3
  7. Thread1 从 aList 读取值 3 --- ,然后附加 4

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

print 语句一样,这可以通过让线程在执行 aList.append(aList[-1] + 1) lock 来防止

(请注意, list.append 方法在纯 python 代码中 线程安全 的,因此不存在附加值可能被破坏的风险。)

原文由 snakecharmerb 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏