【问题标题】:Python multithreading list append gives unexpected resultsPython多线程列表追加给出了意想不到的结果
【发布时间】:2017-05-16 15:42:25
【问题描述】:

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

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]?

【问题讨论】:

    标签: python multithreading list


    【解决方案1】:

    总结

    为什么输出混乱?

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

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

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

    输出

    输出很乱,因为它是由 python2 的print 语句生成的 从线程内部,print 语句不是threadsafe。这意味着 当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] if语句

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

    以原子方式执行,即当前线程不让步给另一个线程 线程也在读取并附加到aList

    但这不是线程的工作方式。线程可能会在读取后屈服 aList 的最后一个元素或增加值,所以它是相当的 可能会有这样的一系列事件:

    1. Thread1 从aList 读取值2
    2. 线程 1 产生
    3. Thread2 从aList 读取值2,然后追加3
    4. Thread2 从aList 读取值3,然后追加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方法 threadsafe在纯python代码中,所以不存在被附加的值可能被破坏的风险。)

    【讨论】:

      【解决方案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

      【讨论】:

      • 真的未定义吗?列表当然是线程安全的。并发写入标准输出肯定是不可预测的,但已定义。
      • 列表是线程安全的。您的第一个陈述是正确的 - 由于在检索 )alist[-1] 和对 append 的调用之间存在其他操作,列表编号不会单调增长。但是“混乱”的输出是由于 print 语句:那个不是线程安全的,并且两个调用 print 的输出都被破坏了。不过,生成的 ist 仍应仅包含 int 对象。
      【解决方案3】:

      由于您使用相同的变量进行读写,它会有一个未定义的行为,我执行了代码并在同一台机器上的两个不同实例上得到了 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

      【讨论】:

      • 好的,感谢您的回答,但是您能提供一些实现同步的代码吗?
      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-05-19
        • 2017-01-05
        • 2021-10-14
        • 1970-01-01
        • 1970-01-01
        • 2020-11-23
        • 2021-10-23
        • 2021-11-23
        相关资源
        最近更新 更多