subprocess的目的就是啟動一個新的進程并且與之通信.
subprocess模塊中只定義了一個類: Popen卦方∪怕ィ可以使用Popen來創(chuàng)建進程蓬坡,并與進程進行復雜的交互燃少。它的構(gòu)造函數(shù)如下:
subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)
參數(shù)args可以是字符串或者序列類型(如:list太惠,元組)氛琢,用于指定進程的可執(zhí)行文件及其參數(shù)黄虱。如果是序列類型袭艺,第一個元素通常是可執(zhí)行文件的路徑。我們也可以顯式的使用executeable參數(shù)來指定可執(zhí)行文件的路徑佑稠。
參數(shù)stdin, stdout, stderr分別表示程序的標準輸入秒梅、輸出、錯誤句柄舌胶。他們可以是PIPE捆蜀,文件描述符或文件對象,也可以設(shè)置為None,表示從父進程繼承辆它。
如果參數(shù)shell設(shè)為true誊薄,程序?qū)⑼ㄟ^shell來執(zhí)行。
參數(shù)env是字典類型娩井,用于指定子進程的環(huán)境變量暇屋。如果env = None,子進程的環(huán)境變量將從父進程中繼承洞辣。
subprocess.PIPE
在創(chuàng)建Popen對象時咐刨,subprocess.PIPE可以初始化stdin, stdout或stderr參數(shù)。表示與子進程通信的標準流扬霜。
subprocess.STDOUT
創(chuàng)建Popen對象時定鸟,用于初始化stderr參數(shù),表示將錯誤通過標準輸出流輸出著瓶。
Popen的方法:
Popen.poll()
用于檢查子進程是否已經(jīng)結(jié)束联予。設(shè)置并返回returncode屬性。
Popen.wait()
等待子進程結(jié)束材原。設(shè)置并返回returncode屬性沸久。
Popen.communicate(input=None)
與子進程進行交互。向stdin發(fā)送數(shù)據(jù)余蟹,或從stdout和stderr中讀取數(shù)據(jù)卷胯。可選參數(shù)input指定發(fā)送到子進程的參數(shù)威酒。Communicate()返回一個元組:(stdoutdata, stderrdata)窑睁。注意:如果希望通過進程的stdin向其發(fā)送數(shù)據(jù),在創(chuàng)建Popen對象的時候葵孤,參數(shù)stdin必須被設(shè)置為PIPE担钮。同樣,如果希望從stdout和stderr獲取數(shù)據(jù)尤仍,必須將stdout和stderr設(shè)置為PIPE箫津。
Popen.send_signal(signal)
向子進程發(fā)送信號。
Popen.terminate()
停止(stop)子進程宰啦。在windows平臺下鲤嫡,該方法將調(diào)用Windows API TerminateProcess()來結(jié)束子進程。
Popen.kill()
殺死子進程绑莺。
Popen.stdin,Popen.stdout 惕耕,Popen.stderr 纺裁,官方文檔上這么說:
stdin, stdout and stderr specify the executed programs’ standard input, standard output and standard error file handles, respectively. Valid values are PIPE, an existing file descriptor (a positive integer), an existing file object, and None.
Popen.pid
獲取子進程的進程ID。
Popen.returncode
獲取進程的返回值。如果進程還沒有結(jié)束欺缘,返回None栋豫。
多線程
閱讀: 8588
多任務(wù)可以由多進程完成,也可以由一個進程內(nèi)的多線程完成谚殊。
我們前面提到了進程是由若干線程組成的丧鸯,一個進程至少有一個線程。
由于線程是操作系統(tǒng)直接支持的執(zhí)行單元嫩絮,因此丛肢,高級語言通常都內(nèi)置多線程的支持,Python也不例外剿干,并且蜂怎,Python的線程是真正的Posix Thread,而不是模擬出來的線程置尔。
Python的標準庫提供了兩個模塊:_thread和threading杠步,_thread是低級模塊,threading是高級模塊榜轿,對_thread進行了封裝幽歼。絕大多數(shù)情況下,我們只需要使用threading這個高級模塊谬盐。
啟動一個線程就是把一個函數(shù)傳入并創(chuàng)建Thread實例甸私,然后調(diào)用start()開始執(zhí)行:
import time, threading
新線程執(zhí)行的代碼:
def loop():
print('thread %s is running...' % threading.current_thread().name)
n = 0
while n < 5:
n = n + 1
print('thread %s >>> %s' % (threading.current_thread().name, n))
time.sleep(1)
print('thread %s ended.' % threading.current_thread().name)
print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)
執(zhí)行結(jié)果如下:
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.
由于任何進程默認就會啟動一個線程,我們把該線程稱為主線程设褐,主線程又可以啟動新的線程颠蕴,Python的threading模塊有個current_thread()函數(shù),它永遠返回當前線程的實例助析。主線程實例的名字叫MainThread犀被,子線程的名字在創(chuàng)建時指定,我們用LoopThread命名子線程外冀。名字僅僅在打印時用來顯示寡键,完全沒有其他意義,如果不起名字Python就自動給線程命名為Thread-1雪隧,Thread-2……
Lock
多線程和多進程最大的不同在于西轩,多進程中,同一個變量脑沿,各自有一份拷貝存在于每個進程中藕畔,互不影響,而多線程中庄拇,所有變量都由所有線程共享注服,所以韭邓,任何一個變量都可以被任何一個線程修改,因此溶弟,線程之間共享數(shù)據(jù)最大的危險在于多個線程同時改一個變量女淑,把內(nèi)容給改亂了。
import time, threading
假定這是你的銀行存款:
balance = 0
def change_it(n):
# 先存后取辜御,結(jié)果應(yīng)該為0:
global balance
balance = balance + n
balance = balance - n
def run_thread(n):
for i in range(100000):
change_it(n)
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
我們定義了一個共享變量balance鸭你,初始值為0,并且啟動兩個線程擒权,先存后取袱巨,理論上結(jié)果應(yīng)該為0,但是菜拓,由于線程的調(diào)度是由操作系統(tǒng)決定的瓣窄,當t1、t2交替執(zhí)行時纳鼎,只要循環(huán)次數(shù)足夠多俺夕,balance的結(jié)果就不一定是0了。
原因是因為高級語言的一條語句在CPU執(zhí)行時是若干條語句贱鄙,即使一個簡單的計算:
balance = balance + n
也分兩步:
計算balance + n劝贸,存入臨時變量中;
將臨時變量的值賦給balance逗宁。
也就是可以看成:
x = balance + n
balance = x
由于x是局部變量映九,兩個線程各自都有自己的x,當代碼正常執(zhí)行時:
初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t2: x2 = balance - 8 # x2 = 8 - 8 = 0
t2: balance = x2 # balance = 0
結(jié)果 balance = 0
但是t1和t2是交替運行的瞎颗,如果操作系統(tǒng)以下面的順序執(zhí)行t1件甥、t2:
初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance - 8 # x2 = 0 - 8 = -8
t2: balance = x2 # balance = -8
結(jié)果 balance = -8
究其原因,是因為修改balance需要多條語句哼拔,而執(zhí)行這幾條語句時引有,線程可能中斷,從而導致多個線程把同一個對象的內(nèi)容改亂了倦逐。
兩個線程同時一存一取譬正,就可能導致余額不對,你肯定不希望你的銀行存款莫名其妙地變成了負數(shù)檬姥,所以曾我,我們必須確保一個線程在修改balance的時候,別的線程一定不能改健民。
如果我們要確保balance計算正確抒巢,就要給change_it()上一把鎖,當某個線程開始執(zhí)行change_it()時秉犹,我們說虐秦,該線程因為獲得了鎖平酿,因此其他線程不能同時執(zhí)行change_it(),只能等待悦陋,直到鎖被釋放后,獲得該鎖以后才能改筑辨。由于鎖只有一個俺驶,無論多少線程,同一時刻最多只有一個線程持有該鎖棍辕,所以暮现,不會造成修改的沖突。創(chuàng)建一個鎖就是通過threading.Lock()來實現(xiàn):
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要獲取鎖:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要釋放鎖:
lock.release()
當多個線程同時執(zhí)行l(wèi)ock.acquire()時楚昭,只有一個線程能成功地獲取鎖栖袋,然后繼續(xù)執(zhí)行代碼,其他線程就繼續(xù)等待直到獲得鎖為止抚太。
獲得鎖的線程用完后一定要釋放鎖塘幅,否則那些苦苦等待鎖的線程將永遠等待下去,成為死線程尿贫。所以我們用try...finally來確保鎖一定會被釋放电媳。
鎖的好處就是確保了某段關(guān)鍵代碼只能由一個線程從頭到尾完整地執(zhí)行,壞處當然也很多庆亡,首先是阻止了多線程并發(fā)執(zhí)行匾乓,包含鎖的某段代碼實際上只能以單線程模式執(zhí)行,效率就大大地下降了又谋。其次拼缝,由于可以存在多個鎖,不同的線程持有不同的鎖彰亥,并試圖獲取對方持有的鎖時咧七,可能會造成死鎖,導致多個線程全部掛起剩愧,既不能執(zhí)行猪叙,也無法結(jié)束,只能靠操作系統(tǒng)強制終止仁卷。