1. subprocess模塊介紹
subprocess模塊可用於產生進程,並連接到進程的輸入/輸出/錯誤輸出管道,並獲取進程的返回值。
該模塊的完整描述,參考Python文檔。
2. subprocess模塊中的常量
PIPE: 一個特殊的值,指示應該創建一個管道
STDOUT: 指示stderr應該輸出到stdout中
3. subprocess模塊主要的API(4個)
(1)call (…) 運行一個命令,等待該命令執行完成(阻塞等待),然後返回該命令執行的返回碼。
可以看出,call函數是構造了一個Popen對象,然後調用該對象的wait方法,阻塞等待,並返回命令執行的返回值。
(2)check_call (…) 類似於call(),但是會拋出CalledProcessError()異常,如果命令執行的返回碼不是0的話。CalledProcessError對象會將返回碼記錄在returncode屬性中。
可以看到,check_call中調用了call方法,因此check_call方法也是創建了進程了之後,阻塞等待,第一句:retcode =call(*popenargs, **kwargs),執行的是 retcode = Popen(*popenargs,**kwargs).wait()
如果線程退出值爲0,則返回0;否則拋出CalledProcessError異常。
(3)check_output (…) 類似於check_call,但是該函數返回的是命令執行的輸出內容,而不是返回值。
check_output()函數中,調用的是Popen對象的communicate方法,而不是調用wait方法,所以不是阻塞的。Popen對象的communicate方法返回一個元組(stdoutdata, stderrdata)
(4) Popen (…) 一個類,用於靈活地在一個新的進程中執行命令
4. class Popen
(1)Popen類的構造函數及其參數解釋
參數:
args:一個字符串,或程序參數列表
bufsize: 當創建標準輸入/標準輸出/標準錯誤輸出 管道文件對象時,該參數作爲open()函數的緩衝區參數
executable:要執行的替換程序。一般不用
stdin,stdout, stderr: 分別指示要執行的程序標準輸入、標準輸出、標準錯誤輸出文件的句柄
preexec_fn:(POSIX only),An object to be called in the childprocess just before the child is executed. 在子進程中調用的對象,僅在子進程執行(execuate)之前調用。鉤子函數,在fork和exec之間執行。
close_fds:控制文件描述符的關閉和繼承
shell:如果設爲True,該命令會通過shell執行
cwd:在子進程執行之前,設置當前的工作目錄
env:爲新的進程定義環境變量
universal_newline:如果爲True,在標準輸入、輸出、錯誤輸出文件對象中,使用同一的行結束符 \n。
startupinfo和creationflags,僅在windows中使用
(2)Popen實例屬性
def __init__(self, args, ,,, stdin, stdout,stderr,)
(3)communicate
和進程交互:給stdin發送數據,從stdout和stderr讀取數據,直到讀到end of file。等待進程種植。可選的輸入參數爲發送給子進程的一個字符串,如果沒有要發送給子進程的數據的話,爲空
check_output()函數中,調用Popen實例process的communicate方法
def _communicate(self, input): stdout = None # Return stderr = None # Return
if self.stdout: stdout = [] stdout_thread = threading.Thread(target=self._readerthread, args=(self.stdout, stdout)) stdout_thread.setDaemon(True) # Thread對象的setDaemon方法 stdout_thread.start() if self.stderr: stderr = [] stderr_thread = threading.Thread(target=self._readerthread, args=(self.stderr, stderr)) stderr_thread.setDaemon(True) stderr_thread.start()
if self.stdin: if input is not None: try: self.stdin.write(input) except IOError as e: if e.errno == errno.EPIPE: # communicate() should ignore broken pipe error pass elif e.errno == errno.EINVAL: # bpo-19612, bpo-30418: On Windows, stdin.write() # fails with EINVAL if the child process exited or # if the child process is still running but closed # the pipe. pass else: raise self.stdin.close()
if self.stdout: stdout_thread.join() if self.stderr: stderr_thread.join()
# All data exchanged. Translate lists into strings. if stdout is not None: stdout = stdout[0] if stderr is not None: stderr = stderr[0]
# Translate newlines, if requested. We cannot let the file # object do the translation: It is based on stdio, which is # impossible to combine with select (unless forcing no # buffering). if self.universal_newlines and hasattr(file, 'newlines'): if stdout: stdout = self._translate_newlines(stdout) if stderr: stderr = self._translate_newlines(stderr)
self.wait() return (stdout, stderr) |
(4)poll
檢查子進程是否結束,設置並返回returncode屬性
5. 進程和線程
轉自:
對於操作系統來說,一個任務就是一個進程(process),比如打開一個瀏覽器就啓動一個瀏覽器進程,打開一個記事本就啓動了一個記事本進程,打開兩個記事本就啓動了兩個記事本進程,打開一個word就啓動了一個word進程。
有些進程還不只同時幹一件事,這就是由一個進程內的多個線程完成的。比如word, 它可以同時進行打字和拼寫檢查等。
多線程的執行方式和多進程是一樣的,也是由操作系統在多個線程之間快速切換,讓每個線程都短暫地交替運行,看起來就像同時執行一樣。當然,真正地同時執行多線程需要多核CPU才能實現。
同時執行多個任務,通常各個任務之間並不是沒有關聯的,而是需要相互通信和協調,有時,任務1必須暫時等待任務2完成後才能繼續執行,有時,任務3和任務4又不能同時執行。
因此,一般不是迫不得已,我們也不想編寫多任務。但是,有很多時候,沒有多任務還真不行。比如在電腦上看電影,就必須由一個線程播放視頻,另一個線程播放音頻,否則,單線程實現的話,只能先把視頻播放完再播放音頻。
python既支持多進程,又支持多線程。
線程是最小的執行單元,即CPU調度的最小單元,進程由一個或多個線程組成。如何調度線程和進程,完全由操作系統決定,程序自己不能決定什麼時候執行,執行多長時間。
多線程和多進程的程序涉及到同步、數據共享的問題。
join方法可以等待子進程結束後再繼續往下執行,通常用於進程間的同步。
6. lock()
轉自:
多線程和多進程最大的不同在於,多進程中,同一個變量,各自有一份拷貝存在每個進程中,互不影響;而多線程中,所有變量都由線程共享,所以,任何一個變量都可以被任何一個線程修改,因此,線程之間共享數據最大的危險在於多個線程同時改一個變量,把內容給改亂了。
來看多個線程同時操作一個變量,怎麼把內存給該亂了:
importtime, threading
#假定這是你的銀行存款
balance= 0
defchange_it(n):
#先存後取,結果應該爲0
global balance
balance= balance + n
balance= balance - n
defrun_thread(n):
forI 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(blance)
理論上結果應該爲0,但是由於線程的調度是由操作系統決定的,當t1, t2交替執行時,只要循環次數足夠多,balance的結果就不一定是0了。
原因是因爲高級語言的一條語句在CPU執行時,是若干條語句,即使是一個簡單的計算:balance = balance + n, 也分兩步: 1)計算balance + n, 存入臨時變量中;2)將臨時變量的值賦給balance。
也就是說,可以看成:
x= balance + n
balance= x
由於x 是局部變量,兩個線程各自都有自己的x,當代碼正常執行時,
但是,t1和t2是交替運行的,如果操作系統以下面的順序執行t1, t2:
究其原因,是因爲修改balance需要多條語句(高級語言是一條,但CPU執行時可能是多條),而執行者幾條語句時,線程可能會中斷,從而導致多個線程把同一個對象的內容改亂了。
兩個線程同時一存一取,就可能導致餘額不對,所以我們必須確保一個線程在修改balance的時候,別的線程一定不能改;也就是說,在修改balance的操作上,不能並行,執行串行。
如果我們要確保balance計算正確,就要給change_it()上一把鎖,當某個線程開始執行change_it()時,我們說該線程因爲獲得了鎖,所以其他線程不能同時執行change_it(),只能等待。同一時刻最多隻有一個線程持有該鎖,所以不會造成修改的衝突。創建一個鎖就是通過threading.Lock()來實現:
balance = 0 lock = threading.Lock()
def run_thread(n): for i in range(100000): # 先要獲取鎖: lock.acquire() try: # 放心地改吧: change_it(n) finally: # 改完了一定要釋放鎖: lock.release()
|
獲得鎖的線程用完後一定要釋放鎖,否則哪些苦苦等待鎖的線程將永遠等待下去,稱爲死線程。所以我們用try… finally來確保鎖一定會被釋放。
鎖的好處就是確保了某段關鍵代碼只能由一個線程從頭到尾完整地執行,壞處當然也很多,首先是阻止了多線程併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大下降了。其次,由於可以存在多個鎖,不同的線程持有不同的鎖,並試圖獲取對方持有的鎖時,可能會造成死鎖,導致多個線程全部掛起,既不能執行,也無法結束,只能靠操作系統強制終止。
多核CPU
如果你不幸擁有一個多核CPU,你肯定在想,多核應該可以同時執行多個線程。
如果寫一個死循環的話,會出現什麼情況呢?
打開Mac OS X的Activity Monitor,或者Windows的Task Manager,都可以監控某個進程的CPU使用率。我們可以監控到一個死循環線程會100%佔用一個CPU。如果有兩個死循環線程,在多核CPU中,可以監控到會佔用200%的CPU,也就是佔用兩個CPU核心。要想把N核CPU的核心全部跑滿,就必須啓動N個死循環線程。
試試用Python寫個死循環:
import threading, multiprocessing
def loop():
x= 0
while True:
x = x ^ 1
for i inrange(multiprocessing.cpu_count()):
t= threading.Thread(target=loop)
t.start()
啓動與CPU核心數量相同的N個線程,在4核CPU上可以監控到CPU佔用率僅有160%,也就是使用不到兩核。
即使啓動100個線程,使用率也就170%左右,仍然不到兩核。
但是用C、C++或Java來改寫相同的死循環,直接可以把全部核心跑滿,4核就跑到400%,8核就跑到800%,爲什麼Python不行呢?
因爲Python的線程雖然是真正的線程,但解釋器執行代碼時,有一個GIL鎖:Global Interpreter Lock,任何Python線程執行前,必須先獲得GIL鎖,然後,每執行100條字節碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執行。這個GIL全局鎖實際上把所有線程的執行代碼都給上了鎖,所以,多線程在Python中只能交替執行,即使100個線程跑在100核CPU上,也只能用到1個核。
GIL是Python解釋器設計的歷史遺留問題,通常我們用的解釋器是官方實現的CPython,要真正利用多核,除非重寫一個不帶GIL的解釋器。
所以,在Python中,可以使用多線程,但不要指望能有效利用多核。如果一定要通過多線程利用多核,那隻能通過C擴展來實現,不過這樣就失去了Python簡單易用的特點。
不過,也不用過於擔心,Python雖然不能利用多線程實現多核任務,但可以通過多進程實現多核任務。多個Python進程有各自獨立的GIL鎖,互不影響。
多線程編程,模型複雜,容易發生衝突,必須用鎖加以隔離,同時,又要小心死鎖的發生。
Python解釋器由於設計時有GIL全局鎖,導致了多線程無法利用多核。多線程的併發在Python中就是一個美麗的夢。
7. python 中的global關鍵字
python中global關鍵字主要用於聲明變量的作用域
在C語言中,由於變量一定是先聲明,後使用,所以我們可以清楚的知道,現在使用的變量是全局還是局部;在python中,變量不需要先聲明,直接使用即可,那我們怎麼知道用的是局部變量還是全局變量呢?首先,python使用的變量,在默認情況下一定是局部變量。其次,python如果想使用作用域之外的全局變量,需要加global前綴。
如,不用global的情況:
a= 5
deftest():
a = 1 局部變量,和外部的a不是一個變量(list類型除外)
print“In test function : a = %d”% a
test()
print“global a = %d ”% a
程序執行結果爲:
Intest function: a = 1
globala = 5
可以看出,不加global的時候,在函數內部是改不了外面的全局變量的(list類型除外)
下面是加了global前綴的情況:
a= 5
deftest():
#告訴執行引擎,我要用全局變量a
globala
a= 1
print“In test func: a = %d ”% a
test()
print“Global a = %d ”% a
可以看出,在函數test的內部,成功修改了全局變量的數值
事實上,網絡上很多文章推崇另外的一種方法來使用全局變量:使用單獨的global文件。
方法如下:
1)在同一個文件夾下,新建兩個文件; myglobal.py, test.py
#myglobal.py
a= 0
b= 1
c= 2
d= 3
#test.py
importglobal.py
deftest():
myglobal.a =100
print“myglobal a = %d” % myglobal.a
test()
print“after test, myglobal a = %d”% myglobal.a
轉自 https://blog.csdn.net/diaoxuesong/article/details/42552943
8. python 常量(沒有專門定義常量的方式)
這是subprocess.py文件中的兩個常量。
python中沒有專門定義常量的方式,通常使用大寫變量名錶示,僅僅是一種提示效果,本質還是變量。如NAME = ‘tony’
參考:
https://blog.csdn.net/bcfdsagbfcisbg/article/details/78134172?locationNum=7&fps=1
https://blog.csdn.net/wtq1993/article/details/51194119