我這個星期一直在探索在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/) –