標籤:
Python多進程方面涉及的模組主要包括:
- subprocess:可以在當前程式中執行其他程式或命令;
- mmap:提供一種基於記憶體的處理序間通訊機制;
- multiprocessing:提供支援多處理器技術的多進程編程介面,並且介面的設計最大程度地保持了和threading模組的一致,便於理解和使用。
本文主要介紹 subprocess 模組及其提供的 Popen 類,以及如何使用該構造器在一個進程中建立新的子進程。此外,還會簡要介紹 subprocess 模組提供的其他方法與屬性,這些功能上雖然沒有 Popen 強大的工具,在某些情況下卻非常方便和高效。
本文的目錄如下:
1. subprocess.Popen 類
2. Popen 對象的屬性
3. Popen 對象的方法
4. subprocess模組的其他簡便方法
5. subprocess模組的其他屬性
6. subprocess模組定義的異常
subprocess.Popen 類
通過調用:
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)
建立並返回一個子進程,並在這個進程中執行指定的程式。
執行個體化 Popen 可以通過許多參數詳細定製子進程的環境,但是只有一個參數是必須的,即位置參數 args ,下面也會詳細介紹剩餘的具名參數。
參數介紹:
- args:要執行的命令或可執行檔的路徑。一個由字串組成的序列(通常是列表),列表的第一個元素是可執行程式的路徑,剩下的是傳給這個程式的參數,如果沒有要傳給這個程式的參數,args 參數可以僅僅是一個字串。
- bufsize:控制 stdin, stdout, stderr 等參數指定的檔案的緩衝,和開啟檔案的 open()函數中的參數 bufsize 含義相同。
- executable:如果這個參數不是 None,將替代參數 args 作為可執行程式;
- stdin:指定子進程的標準輸入;
- stdout:指定子進程的標準輸出;
- stderr:指定子進程的標準錯誤輸出;
對於 stdin, stdout 和 stderr 而言,如果他們是 None(預設情況),那麼子進程使用和父進程相同的標準流檔案。
父進程如果想要和子進程通過 communicate() 方法通訊,對應的參數必須是 subprocess.PIPE(見下文例4);
當然 stdin, stdout 和 stderr 也可以是已經開啟的 file 對象,前提是以合理的方式開啟,比如 stdin 對應的檔案必須要可讀等。
- preexec_fn:預設是None,否則必須是一個函數或者可調用對象,在子進程中首先執行這個函數,然後再去執行為子進程指定的程式或Shell。
- close_fds:布爾型變數,為 True 時,在子進程執行前強制關閉所有除 stdin,stdout和stderr外的檔案;
- shell:布爾型變數,明確要求使用shell運行程式,與參數 executable 一同指定子進程運行在什麼 Shell 中——如果executable=None 而 shell=True,則使用 /bin/sh 來執行 args 指定的程式;也就是說,Python首先起一個shell,再用這個shell來解釋指定啟動並執行命令。
- cwd:代表路徑的字串,指定子進程啟動並執行工作目錄,要求這個目錄必須存在;
- env:字典,鍵和值都是為子進程定義環境變數的字串;
- universal_newline:布爾型變數,為 True 時,stdout 和 stderr 以通用換行(universal newline)模式開啟,
- startupinfo:見下一個參數;
- creationfalgs:最後這兩個參數是Windows中才有的參數,傳遞給Win32的CreateProcess API調用。
同 Linux 中建立子進程類似,父進程建立完子進程後,並不會自動等待子進程執行,父進程在子進程之前推出將導致子進程成為孤兒進程,孤兒進程統一由 init 進程接管,負責其終止後的回收工作。
如果父進程在子進程之後終止,但子進程終止時父進程沒有進行最後的回收工作,子進程殘留的資料結構稱為殭屍進程。大量殭屍進程將耗費系統資源,因此父進程及時等待和回收子進程是必要的,除非能夠確認自己比子進程先終止,從而將回收工作過渡給 init 進程。
這個等待和回收子進程的操作就是wait()函數,下文中將會介紹。
例1:
建立一個子進程,然後執行一個簡單的命令
>>> import subprocess>>> p = subprocess.Popen(‘ls -l‘, shell=True)>>> total 164-rw-r--r-- 1 root root 133 Jul 4 16:25 admin-openrc.sh-rw-r--r-- 1 root root 268 Jul 10 15:55 admin-openrc-v3.sh...>>> p.returncode>>> p.wait()0>>> p.returncode0
這裡也可以使用 p = subprocess.Popen([‘ls‘, ‘-cl‘]) 來建立子進程。
Popen 對象的屬性
Popen建立的子進程有一些有用的屬性,假設 p 是 Popen 建立的子進程,p 的屬性包括:
1.
p.pid
子進程的PID。
2.
p.returncode
該屬性工作表示子進程的返回狀態,returncode可能有多重情況:
- None —— 子進程尚未結束;
- ==0 —— 子進程正常退出;
- > 0—— 子進程異常退出,returncode對應於出錯碼;
- < 0—— 子進程被訊號殺掉了。
3.
p.stdin, p.stdout, p.stderr
子進程對應的一些初始檔案,如果調用Popen()的時候對應的參數是subprocess.PIPE,則這裡對應的屬性是一個包裹了這個管道的 file 對象,
Popen 對象的方法
1.
p.poll()
檢查子進程 p 是否已經終止,返回 p.returncode 屬性 (參考下文 Popen 對象的屬性);
2.
p.wait()
等待子進程 p 終止,返回 p.returncode 屬性;
注意:
wait() 立即阻塞父進程,直到子進程結束!
3.
p.communicate(input=None)
和子進程 p 交流,將參數 input (字串)中的資料發送到子進程的 stdin,同時從子進程的 stdout 和 stderr 讀取資料,直到EOF。
傳回值:
二元組 (stdoutdata, stderrdata) 分別表示從標準出和標準錯誤中讀出的資料。
父進程調用 p.communicate() 和子進程通訊有以下限制:
(1) 只能通過管道和子進程通訊,也就是說,只有調用 Popen() 建立子進程的時候參數 stdin=subprocess.PIPE,才能通過 p.communicate(input) 向子進程的 stdin 發送資料;只有參數 stout 和 stderr 也都為 subprocess.PIPE ,才能通過p.communicate() 從子進程接收資料,否則接收到的二元組中,對應的位置是None。
(2)父進程從子進程讀到的資料緩衝在記憶體中,因此commucate()不適合與子進程交換過大的資料。
注意:
communicate() 立即阻塞父進程,直到子進程結束!
4.
p.send_signal(signal)
向子進程發送訊號 signal;
5.
p.terminate()
終止子進程 p ,等於向子進程發送 SIGTERM 訊號;
6.
p.kill()
殺死子進程 p ,等於向子進程發送 SIGKILL 訊號;
subprocess模組的其他方法
1.
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False)
父進程直接建立子進程執行程式,然後等待子進程完成
傳回值:
call() 返回子進程的 退出狀態 即 child.returncode 屬性;
2.
subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False)
父進程直接建立子進程執行程式,然後等待子進程完成,具體可以使用的參數,參考上文 Popen 類的介紹。
傳回值:
無論子進程是否成功,該函數都返回 0;但是
如果子進程的退出狀態不是0,check_call() 拋出異常 CalledProcessError,異常對象中包含了 child.returncode 對應的返回碼。
例2:
check_call()正常與錯誤執行命令
>>> p = subprocess.check_call([‘ping‘ ,‘-c‘, ‘2‘, ‘www.baidu.com‘])PING www.a.shifen.com (220.181.111.188) 56(84) bytes of data.64 bytes from 220.181.111.188: icmp_seq=1 ttl=42 time=37.4 ms64 bytes from 220.181.111.188: icmp_seq=2 ttl=42 time=37.3 ms--- www.a.shifen.com ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 1001msrtt min/avg/max/mdev = 37.335/37.410/37.486/0.207 ms>>> print p0>>> p = subprocess.check_call([‘ping‘ ,‘-c‘, ‘4‘, ‘[email protected]$#@!(*^.com‘])ping: unknown host [email protected]$#@!(*^.comTraceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.7/subprocess.py", line 540, in check_call raise CalledProcessError(retcode, cmd)subprocess.CalledProcessError: Command ‘[‘ping‘, ‘-c‘, ‘4‘, ‘[email protected]$#@!(*^.com‘]‘ returned non-zero exit status 2>>> print p0
3.
subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False)
父進程直接建立子進程執行程式,以字串的形式返回子進程的輸出。
傳回值:
字串形式的子進程的輸出結果,但是,
如果子進程的 退出狀態 不是0,那麼拋出異常 CalledProcessError,異常對象中包含了 child.returncode 對應的返回碼。
注意:
check_output() 的函數簽名中沒有參數 stdout,調用該方法時,子進程的輸出預設就返回給父進程。
例3:
check_output() 調用的子進程正常與錯誤退出
>>> subprocess.check_output(["echo", "Hello World!"])‘Hello World!\n‘>>> subprocess.check_output("exit 1", shell=True)Traceback (most recent call last): ...subprocess.CalledProcessError: Command ‘exit 1‘ returned non-zero exit status 1
注意:
使用上面提到的三個方法:call()、check_call() 和 check_output() 時,盡量不要將參數 stderr 和 stdout 設定為 subprocess.PIPE,這幾個函數預設都會等待子進程完成,子進程產生大量的輸出資料如果造成管道堵塞,父進程再等待子進程完成可能造成死結。
subprocess模組的其他屬性
subprocess.PIPE
調用本模組提供的若干函數時,作為 std* 參數的值,為標準流檔案開啟一個管道。
例4:
使用管道串連標準流檔案
import subprocesschild1 = subprocess.Popen([‘ls‘, ‘-l‘], stdout=subprocess.PIPE)child2 = subprocess.Popen([‘wc‘, ‘-l‘], stdin=child1.stdout, stdout=subprocess.PIPE)out = child2.communicate()child1.wait()child2.wait()print(out)
這裡將子進程 child1 的標準輸出作為子進程 child2 的標準輸入,父進程通過 communicate() 讀取 child2 的標準輸出後列印。
subprocess.STDOUT
調用本模組提供的若干函數時,作為 stderr 參數的值,將子進程的標準錯誤輸出列印到標準輸出。
subprocess模組定義的異常
exception subprocess.CalledProcessError
(1)什麼時候可能拋出該異常:調用 check_call() 或 check_output() ,子進程的退出狀態不為 0 時。
(2)該異常包含以下資訊:
- returncode:子進程的退出狀態;
- cmd:建立子進程時指定的命令;
- output:如果是調用 check_output() 時拋出的該異常,這裡包含子進程的輸出,否則該屬性為None。
總結
本文介紹了Python subprocess的基本用法,使用 Popen 可以在Python進程中建立子進程,如果只對子進程的執行退出狀態感興趣,可以調用 subprocess.call() 函數,如果想通過異常處理機制解決子進程異常退出的情形,可以考慮使用 subprocess.check_call() 和 subprocess.check_output。如果希望獲得子進程的輸出,可以調用 subprocess.check_output(),但 Popen() 無疑是功能最強大的。
subprocess模組的缺陷在於預設提供的父子處理序間通訊手段有限,只有管道;同時建立的子進程專門用來執行外部的程式或命令。
Linux下處理序間通訊的手段很多,子進程也完全可能從建立之後繼續調用
Python多進程(1)——subprocess與Popen()