Python多线程列表追加

Ale*_*xey 4 python multithreading

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

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
Run Code Online (Sandbox Code Playgroud)

所以输出是:

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]
Run Code Online (Sandbox Code Playgroud)

为什么它如此凌乱并且列表不等于[1,2,3,4,5,6]?

sna*_*erb 5

摘要

为什么输出混乱?

==>因为线程可能通过执行一条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()
Run Code Online (Sandbox Code Playgroud)

产生以下交错输出:

>>> main()
['foo', 'bar'['foo', , 'bar', 'baz']
'baz']
['foo', ['foo', 'bar''bar', 'baz']
, 'baz']
Run Code Online (Sandbox Code Playgroud)

可以通过使用来防止交错行为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']
Run Code Online (Sandbox Code Playgroud)

清单内容

的最终内容aList将是[1, 2, 3, 4, 5, 6]如果语句

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

是原子执行的,也就是说,当前线程不会屈服于另一个线程,该线程也在读取并附加到aList

但是,这不是线程的工作方式。在从aList值中读取最后一个元素或增加值后,线程可能会屈服,因此很有可能发生如下事件序列:

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

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

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

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


tin*_*ime 3

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

\n\n
\n

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

\n
\n\n

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

\n\n

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

\n\n
\n

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

\n
\n\n

原答案:

\n\n
\n

这是未定义的行为,因为您有多个线程写入同一位内存 - 因此您观察到的“混乱”输出。

\n\n
\n

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

\n\n

我想你已经成功地测试了这个,答案是否定的。\n 关于 SO 有很多更详细的解释:\n /sf/answers/416011921/

\n
\n
\n