1105| subprocess,多線程

http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143192823818768cd506abbc94eb5916192364506fa5d000

subprocess的目的就是啟動一個新的進程并且與之通信.

subprocess模塊中只定義了一個類: Popen。可以使用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)

參數args可以是字符串或者序列類型(如:list,元組),用于指定進程的可執行文件及其參數。如果是序列類型,第一個元素通常是可執行文件的路徑。我們也可以顯式的使用executeable參數來指定可執行文件的路徑。

參數stdin, stdout, stderr分別表示程序的標準輸入、輸出、錯誤句柄。他們可以是PIPE,文件描述符或文件對象,也可以設置為None,表示從父進程繼承。

如果參數shell設為true,程序將通過shell來執行。

參數env是字典類型,用于指定子進程的環境變量。如果env = None,子進程的環境變量將從父進程中繼承。

subprocess.PIPE

在創建Popen對象時,subprocess.PIPE可以初始化stdin, stdout或stderr參數。表示與子進程通信的標準流。

subprocess.STDOUT

創建Popen對象時,用于初始化stderr參數,表示將錯誤通過標準輸出流輸出。

Popen的方法:

Popen.poll()

用于檢查子進程是否已經結束。設置并返回returncode屬性。

Popen.wait()

等待子進程結束。設置并返回returncode屬性。

Popen.communicate(input=None)

與子進程進行交互。向stdin發送數據,或從stdout和stderr中讀取數據??蛇x參數input指定發送到子進程的參數。Communicate()返回一個元組:(stdoutdata, stderrdata)。注意:如果希望通過進程的stdin向其發送數據,在創建Popen對象的時候,參數stdin必須被設置為PIPE。同樣,如果希望從stdout和stderr獲取數據,必須將stdout和stderr設置為PIPE。

Popen.send_signal(signal)

向子進程發送信號。

Popen.terminate()

停止(stop)子進程。在windows平臺下,該方法將調用Windows API TerminateProcess()來結束子進程。

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

獲取進程的返回值。如果進程還沒有結束,返回None。


多線程

閱讀: 8588
多任務可以由多進程完成,也可以由一個進程內的多線程完成。

我們前面提到了進程是由若干線程組成的,一個進程至少有一個線程。

由于線程是操作系統直接支持的執行單元,因此,高級語言通常都內置多線程的支持,Python也不例外,并且,Python的線程是真正的Posix Thread,而不是模擬出來的線程。

Python的標準庫提供了兩個模塊:_thread和threading,_thread是低級模塊,threading是高級模塊,對_thread進行了封裝。絕大多數情況下,我們只需要使用threading這個高級模塊。

啟動一個線程就是把一個函數傳入并創建Thread實例,然后調用start()開始執行:

import time, threading

新線程執行的代碼:

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)
執行結果如下:

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()函數,它永遠返回當前線程的實例。主線程實例的名字叫MainThread,子線程的名字在創建時指定,我們用LoopThread命名子線程。名字僅僅在打印時用來顯示,完全沒有其他意義,如果不起名字Python就自動給線程命名為Thread-1,Thread-2……

Lock

多線程和多進程最大的不同在于,多進程中,同一個變量,各自有一份拷貝存在于每個進程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個變量都可以被任何一個線程修改,因此,線程之間共享數據最大的危險在于多個線程同時改一個變量,把內容給改亂了。

import time, threading

假定這是你的銀行存款:

balance = 0

def change_it(n):
# 先存后取,結果應該為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,并且啟動兩個線程,先存后取,理論上結果應該為0,但是,由于線程的調度是由操作系統決定的,當t1、t2交替執行時,只要循環次數足夠多,balance的結果就不一定是0了。

原因是因為高級語言的一條語句在CPU執行時是若干條語句,即使一個簡單的計算:

balance = balance + n
也分兩步:

計算balance + n,存入臨時變量中;
將臨時變量的值賦給balance。
也就是可以看成:

x = balance + n
balance = x
由于x是局部變量,兩個線程各自都有自己的x,當代碼正常執行時:

初始值 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

結果 balance = 0
但是t1和t2是交替運行的,如果操作系統以下面的順序執行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

結果 balance = -8
究其原因,是因為修改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()
當多個線程同時執行lock.acquire()時,只有一個線程能成功地獲取鎖,然后繼續執行代碼,其他線程就繼續等待直到獲得鎖為止。

獲得鎖的線程用完后一定要釋放鎖,否則那些苦苦等待鎖的線程將永遠等待下去,成為死線程。所以我們用try...finally來確保鎖一定會被釋放。

鎖的好處就是確保了某段關鍵代碼只能由一個線程從頭到尾完整地執行,壞處當然也很多,首先是阻止了多線程并發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地下降了。其次,由于可以存在多個鎖,不同的線程持有不同的鎖,并試圖獲取對方持有的鎖時,可能會造成死鎖,導致多個線程全部掛起,既不能執行,也無法結束,只能靠操作系統強制終止。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 處于學習別人代碼風格階段,github參考學習程序程序開頭會有 一是用來指定腳本語言為 Python,二是用來指定...
    lifesmily閱讀 1,080評論 0 0
  • 我們前面提到了進程是由若干線程組成的,一個進程至少有一個線程。多線程優點: 在一個進程中的多線程和主線程分享相同的...
    第八共同體閱讀 529評論 0 0
  • 進程與線程的區別 現在,多核CPU已經非常普及了,但是,即使過去的單核CPU,也可以執行多任務。由于CPU執行代碼...
    蘇糊閱讀 783評論 0 2
  • 線程狀態新建,就緒,運行,阻塞,死亡。 線程同步多線程可以同時運行多個任務,線程需要共享數據的時候,可能出現數據不...
    KevinCool閱讀 824評論 0 0
  • 1、聽《婷婷唱古詩》 2、讀 閱讀《手指謠》《嬰兒游戲繪本》睡覺啦!《語言啟蒙》字詞擴展《小熊寶寶繪本》拉粑粑。對...
    馬行千里玥溢彩閱讀 151評論 0 0