我这个星期一直在探索在Python内部执行的线程。我很惊讶我每天都不知道自己有多少惊讶,不知道我想知道什么,这就是让我痒。Python 2.X中的print`内建函数原子吗?
我发现了一些在一块的代码,我的Python 2.7下运行的应用程序mutlithreaded奇怪。我们都知道Python 2.7默认在100条虚拟指令之后在线程之间切换。调用一个函数是一个虚拟指令,例如:
>>> from __future__ import print_function
>>> def x(): print('a')
...
>>> dis.dis(x)
1 0 LOAD_GLOBAL 0 (print)
3 LOAD_CONST 1 ('a')
6 CALL_FUNCTION 1
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
正如你可以看到,装载全球print
后装载不断a
函数被调用后。因此调用一个函数是原子的,因为它是用一条指令完成的。因此,在多线程程序或者功能(print
这里)运行或函数获取运行更改前的“运行”线程被中断。即,如果发生上下文切换LOAD_GLOBAL
和LOAD_CONST
之间,指令CALL_FUNCTION
将不会运行。
请记住,在上面的代码中,我使用的是from __future__ import print_function
,我确实调用了内置函数而不是print
声明。让我们来看看功能x
字节代码,但这次与print
声明:
>>> def x(): print "a" # print stmt
...
>>> dis.dis(x)
1 0 LOAD_CONST 1 ('a')
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 0 (None)
8 RETURN_VALUE
它很可能在这种情况下,可能LOAD_CONST
和PRINT_ITEM
之间发生线程上下文切换,有效地防止执行PRINT_NEWLINE
指令。所以,如果你有这样多线程程序(从编程的Python第四版借,略作修改):
def counter(myId, count):
for i in range(count):
time.sleep(1)
print ('[%s] => %s' % (myId, i)) #print (stmt) 2.X
for i in range(5):
thread.start_new_thread(counter, (i, 5))
time.sleep(6) # don't quit early so other threads don't die
输出可能会或可能不会像这取决于线程是如何切换:
[0] => 0
[3] => 0[1] => 0
[4] => 0
[2] => 0
...many more...
这是所有与print
声明没关系。
如果我们改变print
声明与内置print
功能会发生什么?让我们来看看:
from __future__ import print_function
def counter(myId, count):
for i in range(count):
time.sleep(1)
print('[%s] => %s' % (myId, i)) #print builtin (func)
for i in range(5):
thread.start_new_thread(counter, (i, 5))
time.sleep(6)
如果您运行此脚本足够长的时间和多次,你会看到这样的事情:
[4] => 0
[3] => 0[1] => 0
[2] => 0
[0] => 0
...many more...
鉴于以上所有的解释怎么能这样呢? print
现在是一个函数,它如何打印传入的字符串而不是新行?在印刷字符串的末尾的end
的print
打印的值,它的默认设置为\n
。实质上,对功能的调用是原子的,在地球上它是如何被打断的?
让我们打击我们的头脑:
def counter(myId, count):
for i in range(count):
time.sleep(1)
#sys.stdout.write('[%s] => %s\n' % (myId, i))
print('[%s] => %s\n' % (myId, i), end='')
for i in range(5):
thread.start_new_thread(counter, (i, 5))
time.sleep(6)
现在新线总是打印,无冗杂的输出了:
[1] => 0
[2] => 0
[0] => 0
[4] => 0
...many more...
的\n
字符串加入现在显然证明了print
功能不是原子的(即使它是一个函数),本质上它就像是print
声明一样。然而,它会不相干地或愚蠢地告诉我们它是一个简单的函数,因此是一个原子操作?!
注:我从来不靠线程的应用程序的顺序或时间正常工作的。这仅仅是为了测试的目的,并坦率地说,像我这样的怪才。
它**不可能**在一般情况下原子的,因为它能够处理任意大小,这必然包括比操作系统内核将同意在一个单一的系统调用来处理更大尺寸的字符串。 –
https://stackoverflow.com/questions/3029816/how-do-i-get-a-thread-safe-print-in-python-2-6 –
......这就是说 - 只是因为某些东西原子在Python解释器的层并不意味着它在原子层以下的层。 [抽象漏洞](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/) –