python中的進程、線程(threading、multiprocessing、Queue、subprocess)
Python中的進程與線程
學習知識,我們不但要知其然,還是知其所以然。你做到了你就比別人NB。 我們先瞭解一下什麼是進程和線程。 進程與線程的曆史
我們都知道電腦是由硬體和軟體組成的。硬體中的CPU是電腦的核心,它承擔電腦的所有任務。 作業系統是運行在硬體之上的軟體,是電腦的管理者,它負責資源的管理和分配、任務的調度。 程式是運行在系統上的具有某種功能的軟體,比如說瀏覽器,音樂播放器等。 每次執行程式的時候,都會完成一定的功能,比如說瀏覽器幫我們開啟網頁,為了保證其獨立性,就需要一個專門的管理和控制執行程式的資料結構——進程式控制制塊。 進程就是一個程式在一個資料集上的一次動態執行過程。 進程一般由程式、資料集、進程式控制制塊三部分組成。我們編寫的程式用來描述進程要完成哪些功能以及如何完成;資料集則是程式在執行過程中所需要使用的資源;進程式控制制塊用來記錄進程的外部特徵,描述進程的執行變化過程,系統可以利用它來控制和管理進程,它是系統感知進程存在的唯一標誌。
在早期的作業系統裡,電腦只有一個核心,進程執行程式的最小單位,任務調度採用時間片輪轉的搶佔式方式進行進程調度。每個進程都有各自的一塊獨立的記憶體,保證進程彼此間的記憶體位址空間的隔離。 隨著電腦技術的發展,進程出現了很多弊端,一是進程的建立、撤銷和切換的開銷比較大,二是由於對稱式多處理機(對稱式多處理機(SymmetricalMulti-Processing)又叫SMP,是指在一個電腦上彙集了一組處理器(多CPU),各CPU之間共用記憶體子系統以及匯流排結構)的出現,可以滿足多個運行單位,而多進程並行開銷過大。 這個時候就引入了線程的概念。 線程也叫輕量級進程,它是一個基本的CPU執行單元,也是程式執行過程中的最小單元,由線程ID、程式計數器、寄存器集合 和堆棧共同組成。線程的引入減小了程式並發執行時的開銷,提高了作業系統的並發效能。 線程沒有自己的系統資源,只擁有在運行時必不可少的資源。但線程可以與同屬與同一進程的其他線程共用進程所擁有的其他資源。
進程與線程之間的關係
線程是屬於進程的,線程運行在進程空間內,同一進程所產生的線程共用同一記憶體空間,當進程退出時該進程所產生的線程都會被強制退出並清除。線程可與屬於同一進程的其它線程共用進程所擁有的全部資源,但是其本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資訊(如程式計數器、一組寄存器和棧)。 threading模組
threading 模組建立在 _thread 模組之上。thread 模組以低級、原始的方式來處理和控制線程,而 threading 模組通過對 thread 進行二次封裝,提供了更方便的 api 來處理線程。
import threadingimport time def worker(num): """ thread worker function :return: """ time.sleep(1) print("Thread %d" % num) return for i in range(20): t = threading.Thread(target=worker,args=(i,),name=“t.%d” % i) t.start()
thread方法說明
t.start() : 啟用線程,
t.getName() : 擷取線程的名稱
t.setName() : 設定線程的名稱
t.name : 擷取或設定線程的名稱
t.is_alive() : 判斷線程是否為啟用狀態
t.isAlive() :判斷線程是否為啟用狀態
t.setDaemon() 設定為後台線程或前台線程(預設:False);通過一個布爾值設定線程是否為守護線程,必須在執行start()方法之後才可以使用。如果是後台線程,主線程執行過程中,後台線程也在進行,主線程執行完畢後,後台線程不論成功與否,均停止;如果是前台線程,主線程執行過程中,前台線程也在進行,主線程執行完畢後,等待前台線程也執行完成後,程式停止
t.isDaemon() : 判斷是否為守護線程
t.ident :擷取線程的標識符。線程標識符是一個非零整數,只有在調用了start()方法之後該屬性才有效,否則它只返回None。
t.join() :逐個執行每個線程,執行完畢後繼續往下執行,該方法使得多線程變得無意義
t.run() :線程被cpu調度後自動執行線程對象的run方法 線程鎖threading.RLock和threading.Lock
我們使用線程對資料進行操作的時候,如果多個線程同時修改某個資料,可能會出現不可預料的結果,為了保證資料的準確性,引入了鎖的概念。
例:假設列表A的所有元素就為0,當一個線程從前向後列印列表的所有元素,另外一個線程則從後向前修改列表的元素為1,那麼輸出的時候,列表的元素就會一部分為0,一部分為1,這就導致了資料的不一致。鎖的出現解決了這個問題。
import threadingimport time globals_num = 0 lock = threading.RLock() def Func(): lock.acquire() # 獲得鎖 global globals_num globals_num += 1 time.sleep(1) print(globals_num) lock.release() # 釋放鎖 for i in range(10): t = threading.Thread(target=Func) t.start()
threading.RLock和threading.Lock 的區別
RLock允許在同一線程中被多次acquire。而Lock卻不允許這種情況。 如果使用RLock,那麼acquire和release必須成對出現,即調用了n次acquire,必須調用n次的release才能真正釋放所佔用的瑣。
import threadinglock = threading.Lock() #Lock對象lock.acquire()lock.acquire() #產生了死瑣。lock.release()lock.release()
import threadingrLock = threading.RLock() #RLock對象rLock.acquire()rLock.acquire() #在同一線程內,程式不會堵塞。rLock.release()rLock.release()
threading.Event
Event是線程間通訊最間的機制之一:一個線程發送一個event訊號,其他的線程則等待這個訊號。用於主線程式控制制其他線程的執行。 Events 管理一個flag,這個flag可以使用set()設定成True或者使用clear()重設為False,wait()則用於阻塞,在flag為True之前。flag預設為False。 Event.wait([timeout]) : 堵塞線程,直到Event對象內部標識位被設為True或逾時(如果提供了參數timeout)。 Event.set() :將標識位設為Ture Event.clear() : 將標識伴設為False。 Event.isSet() :判斷標識位是否為Ture。
import threading def do(event): print('start') event.wait() print('execute') event_obj = threading.Event()for i in range(10): t = threading.Thread(target=do, args=(event_obj,)) t.start() event_obj.clear()inp = input('input:')if inp == 'true': event_obj.set()
當線程執行的時候,如果flag為False,則線程會阻塞,當flag為True的時候,線程不會阻塞。它提供了本地和遠端並發性。
threading.Condition:
一個condition變數總是與某些類型的鎖相聯絡,這個可以使用預設的情況或建立一個,當幾個condition變數必須共用和同一個鎖的時候,是很有用的。鎖是conditon對象的一部分:沒有必要分別跟蹤。
condition變數服從上下文管理協議:with語句塊封閉之前可以擷取與鎖的聯絡。 acquire() 和 release() 會調用與鎖相關聯的相應的方法。
其他和鎖關聯的方法必須被調用,wait()方法會釋放鎖,當另外一個線程使用 notify() or notify_all()喚醒它之前會一直阻塞。一旦被喚醒,wait()會重新獲得鎖並返回,Condition類實現了一個conditon變數。 這個conditiaon變數允許一個或多個線程等待,直到他們被另一個線程通知。 如果lock參數,被給定一個非空的值,,那麼他必須是一個lock或者Rlock對象,它用來做底層鎖。否則,會建立一個新的Rlock對象,用來做底層鎖。 wait(timeout=None) : 等待通知,或者等到設定的逾時時間。當調用這wait()方法時,如果調用它的線程沒有得到鎖,那麼會拋出一個RuntimeError 異常。 wati()釋放鎖以後,在被調用相同條件的另一個進程用notify() or notify_all() 叫醒之前 會一直阻塞。wait() 還可以指定一個逾時時間。
如果有等待的線程,notify()方法會喚醒一個在等待conditon變數的線程。notify_all() 則會喚醒所有在等待conditon變數的線程。
注意: notify()和notify_all()不會釋放鎖,也就是說,線程被喚醒後不會立刻返回他們的wait() 調用。除非線程調用notify()和notify_all()之後放棄了鎖的所有權。
在典型的設計風格裡,利用condition變數用鎖去通許訪問一些共用狀態,線程在擷取到它想得到的狀態前,會反覆調用wait()。修改狀態的線程在他們狀態改變時調用 notify() or notify_all(),用這種方式,線程會儘可能的擷取到想要的一個等待者狀態。 例子: 生產者-消費者模型, View Code
consumer()線程要等待producer()設定了Condition之後才能繼續。
queue模組
Queue 就是對隊列,它是安全執行緒的
舉例來說,我們去肯德基吃飯。廚房是給我們做飯的地方,前台負責把廚房做好的飯賣給顧客,顧客則去前台領取做好的飯。這裡的前台就相當於我們的隊列。
這個模型也叫生產者-消費者模型。
import queueq = queue.Queue(maxsize=0) # 構造一個先進顯出隊列,maxsize指定隊列長度,為0 時,表示隊列長度無限制。q.join() # 等到隊列為kong的時候,在執行別的操作q.qsize() # 返回隊列的大小 (不可靠)q.empty() # 當隊列為空白的時候,返回True 否則返回False (不可靠)q.full() # 當隊列滿的時候,返回True,否則返回False (不可靠)q.put(item, block=True, timeout=None) # 將item放入Queue尾部,item必須存在,可以參數block預設為True,表示當隊列滿時,會等待隊列給出可用位置, 為False時為非阻塞,此時如果隊列已滿,會引發queue.Full 異常。 選擇性參數timeout,表示 會阻塞設定的時間,過後, 如果隊列無法給出放入item的位置,則引發 queue.Full 異常q.get(block=True, timeout=None) # 移除並返回隊列頭部的一個值,選擇性參數block預設為True,表示擷取值的時候,如果隊列為空白,則阻塞,為False時,不阻塞, 若此時隊列為空白,則引發 queue.Empty異常。 選擇性參數timeout,表示會阻塞設定的時候,過後,如果隊列為空白,則引發Empty異常。q.put_nowait(item) # 等效於 put(item,block=False)q.get_nowait() # 等效於 get(item,block=False)