Python多线程print错乱问题

现象

看如下python2代码:

import time
import threading

def a():
    time.sleep(1)
    print 'hello'

ts = []
for i in range(10):
    t = threading.Thread(target=a)
    t.start()
    ts.append(t)

for t in ts:
    t.join()

运行下输出会错乱



这可能是初次接触python多线程的人经常会遇到的问题。

结论

先说结论:Python2的print关键词不是线程安全的,Python3的print函数是线程安全的。

原因

我们用dis包对某个PyObject做反汇编(disassemble),这里的反汇编其实指的是把python脚本翻译成python中间字节码,由python虚拟机解释执行字节码。虚拟机模拟了cpu,字节码(opcode)则类似汇编语言。字节码解释器是python的核心所在,Python通过GIL(Global Interpreter Lock)这个互斥锁保证同一时刻只有一个线程使用解释器(或者说虚拟机)。

import dis

def a():
    print 'hello'

dis.dis(a)

可以看到a()的字节码如下:


其中print对应两个字节码PRINT_ITEMPRINT_NEWLINE,分别是输出对象和输出换行符,理论上print 'hello'等价于

sys.stdout.write('hello')
sys.stdout.write('\n')

所以当虚拟机做线程切换时可能会把输出hello和输出\n的两个操作切开,造成输出上述错乱的结果,这就是为什么python2的print不是原子操作。
那我们再看如下代码及输出:

import dis
import sys

def a():
    sys.stdout.write('hello'+'\n')

dis.dis(a)


其实write()函数的调用是CALL_FUNCTION这一步,而write()是builtin_function,是原子操作。(可以试试看dis.dis(sys.stdout.write)

如何避免

  1. 使用python3,社区将在2020年放弃维护python2。
  2. 如果需要使用python2,需要将多线程中的print content替换成sys.stdout.write('{}%\n'.format(content))

参考

[1] https://xionchen.github.io/2018/01/29/python-mutli-io/#%E4%BA%8Cprint%E9%94%99%E4%B9%B1%E9%97%AE%E9%A2%98

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容