標籤:timeout enum self mutex 輸出 編譯 parent lob 函數
線程
1.什麼是線程?
線程是作業系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流程,一個進程中可以並發多個線程,每條線程並存執行不同的任務。
2.python GIL全域解譯器鎖(僅需瞭解)
無論你啟多少個線程,你有多少個cpu, Python在執行的時候會淡定的在同一時刻只允許一個線程運行
首先需要明確的一點是GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就好比C++是一套語言(文法)標準,但是可以用不同的編譯器來編譯成可執行代碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也一樣,同樣一段代碼可以通過CPython,PyPy,Psyco等不同的Python執行環境來執行。像其中的JPython就沒有GIL。然而因為CPython是大部分環境下預設的Python執行環境。所以在很多人的概念裡CPython就是Python,也就想當然的把GIL歸結為Python語言的缺陷。所以這裡要先明確一點:GIL並不是Python的特性,Python完全可以不依賴於GIL
這篇文章透徹的剖析了GIL對python多線程的影響,強烈推薦看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf
3.python threading模組
threading模組建立在_thread 模組之上。thread模組以低級、原始的方式來處理和控制線程,而threading 模組通過對thread 進行二次封裝,提供了更方便的 api來處理線程。
線程有兩種調用方式,如下:
1)直接調用
import threading
import time
def sayhi(num): #定義每個線程要啟動並執行函數
print("running on number:%s" %num)
time.sleep(3)
if __name__ == ‘__main__‘:
t1 = threading.Thread(target=sayhi,args=(1,)) #產生一個線程執行個體 target=函數名 args傳元組,元組中是參數
t2 = threading.Thread(target=sayhi,args=(2,)) #產生另一個線程執行個體
t1.start() #啟動線程
t2.start() #啟動另一個線程
print(t1.getName()) #擷取線程名
print(t2.getName())
2)繼承調用
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定義每個線程要啟動並執行函數 print("running on number:%s" %self.num) time.sleep(3) if __name__ == ‘__main__‘: t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start()
Python通過兩個標準庫thread和threading提供對線程的支援。thread提供了低層級的、原始的線程以及一個簡單的鎖。
thread 模組提供的其他方法:
- threading.currentThread(): 返回當前的線程變數。
- threading.enumerate(): 返回一個包含正在啟動並執行線程的list。正在運行指線程啟動後、結束前,不包括啟動前和終止後的線程。
- threading.activeCount(): 返回正在啟動並執行線程數量,與len(threading.enumerate())有相同的結果。
除了使用方法外,線程模組同樣提供了Thread類來處理線程,Thread類提供了以下方法:
- run(): 用以表示線程活動的方法。
- start():啟動線程活動。
- join([time]): 等待至線程中止。這阻塞調用線程直至線程的join() 方法被調用中止-正常退出或者拋出未處理的異常-或者是可選的逾時發生。
- isAlive(): 返回線程是否活動的。
- getName(): 返回線程名。
- setName(): 設定線程名。
4.Join & Daemon
join 等待線程執行完後,其他線程再繼續執行
import threading,time def run(n,sleep_time): print("test...",n) time.sleep(sleep_time) print("test...done", n) if __name__ == ‘__main__‘: t1 = threading.Thread(target=run,args=("t1",2)) t2 = threading.Thread(target=run,args=("t2",3)) # 兩個同時執行,然後等待t1執行完成後,主線程和子線程再開始執行 t1.start() t2.start() t1.join() # 等待t1 print("main thread") # 程式輸出 # test... t1 # test... t2 # test...done t1 # main thread # test...done t2
Daemon 守護進程
t.setDaemon() 設定為後台線程或前台線程(預設:False);通過一個布爾值設定線程是否為守護線程,必須在執行start()方法之後才可以使用。如果是後台線程,主線程執行過程中,後台線程也在進行,主線程執行完畢後,後台線程不論成功與否,均停止;如果是前台線程,主線程執行過程中,前台線程也在進行,主線程執行完畢後,等待前台線程也執行完成後,程式停止
import threading,time def run(n): print(‘[%s]------running----\n‘ % n) time.sleep(2) print(‘--done--‘) def main(): for i in range(5): t = threading.Thread(target=run, args=[i, ]) t.start() t.join(1) print(‘starting thread‘, t.getName()) m = threading.Thread(target=main, args=[]) m.setDaemon(True) # 將main線程設定為Daemon線程,它做為程式主線程的守護線程,當主線程退出時, # m線程也會退出,由m啟動的其它子線程會同時退出,不管是否執行完任務 m.start() m.join(timeout=2) print("---main thread done----") # 程式輸出 # [0]------running---- # starting thread Thread-2 # [1]------running---- # --done-- # ---main thread done----
5.線程鎖(互斥鎖Mutex)
我們使用線程對資料進行操作的時候,如果多個線程同時修改某個資料,可能會出現不可預料的結果,為了保證資料的準確性,引入了鎖的概念。
例:假設列表A的所有元素就為0,當一個線程從前向後列印列表的所有元素,另外一個線程則從後向前修改列表的元素為1,那麼輸出的時候,列表的元素就會一部分為0,一部分為1,這就導致了資料的不一致。鎖的出現解決了這個問題。
不加鎖:
import time import threading def addNum(): global num # 在每個線程中都擷取這個全域變數 print(‘--get num:‘, num) time.sleep(1) num -= 1 # 對此公開變數進行-1操作 num = 100 # 設定一個共用變數 thread_list = [] for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: # 等待所有線程執行完畢 t.join() print(‘final num:‘, num)
加鎖:
import time import threading def addNum(): global num # 在每個線程中都擷取這個全域變數 print(‘--get num:‘, num) time.sleep(1) lock.acquire() # 修改資料前加鎖 num -= 1 # 對此公開變數進行-1操作 lock.release() # 修改後釋放 num = 100 # 設定一個共用變數 thread_list = [] lock = threading.Lock() # 產生全域鎖 for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: # 等待所有線程執行完畢 t.join() print(‘final num:‘, num)
GIL VS LOCK
機智的同學可能會問到這個問題,就是既然你之前說過了,Python已經有一個GIL來保證同一時間只能有一個線程來執行了,為什麼這裡還需要lock? 注意啦,這裡的lock是使用者級的lock,跟那個GIL沒關係 ,具體我們通過來看一下+配合我現場講給大家,就明白了。
6.遞迴鎖
說白了就是在一個大鎖中還要再包含子鎖
import threading,time def run1(): print("grab the first part data") lock.acquire() global num num += 1 lock.release() return num def run2(): print("grab the second part data") lock.acquire() global num2 num2 += 1 lock.release() return num2 def run3(): lock.acquire() res = run1() print(‘--------between run1 and run2-----‘) res2 = run2() lock.release() print(res, res2) if __name__ == ‘__main__‘: num, num2 = 0, 0 lock = threading.RLock() for i in range(10): t = threading.Thread(target=run3) t.start() while threading.active_count() != 1: print(threading.active_count()) else: print(‘----all threads done---‘) print(num, num2)
threading.RLock和threading.Lock 的區別:
RLock允許在同一線程中被多次acquire。而Lock卻不允許這種情況。 如果使用RLock,那麼acquire和release必須成對出現,即調用了n次acquire,必須調用n次的release才能真正釋放所佔用的瑣。
import threading lock = threading.Lock() #Lock對象 lock.acquire() lock.acquire() #產生了死瑣。 lock.release() lock.release()
import threading rLock = threading.RLock() #RLock對象 rLock.acquire() rLock.acquire() #在同一線程內,程式不會堵塞。 rLock.release() rLock.release()
1. 多進程multiprocessing
multiprocessing包是Python中的多流程管理組件,是一個跨平台版本的多進程模組。與threading.Thread類似,它可以利用multiprocessing.Process對象來建立一個進程。該進程可以運行在Python程式內部編寫的函數。該Process對象與Thread對象的用法類似。
建立一個Process執行個體,可用start()方法啟動。
join()方法可以等待子進程結束後再繼續往下運行,通常用於進程間的同步。
from multiprocessing import Process
import time
def f(name):
time.sleep(2)
print(‘hello‘, name)
if __name__ == ‘__main__‘:
p = Process(target=f, args=(‘bob‘,))
p.start()
p.join()
寫個程式,對比下主進程和子進程的ID:
from multiprocessing import Process import os def info(title): print(title) print(‘進程名稱:‘, __name__) print(‘父進程ID:‘, os.getppid()) print(‘子進程ID:‘, os.getpid()) print("\n\n") def f(name): info(‘\033[31;1mcalled from child process function f\033[0m‘) print(‘hello‘, name) if __name__ == ‘__main__‘: info(‘\033[32;1mmain process line\033[0m‘) p = Process(target=f, args=(‘bob‘,)) p.start()
2. 處理序間通訊
不同進程間記憶體是不共用的,要想實現兩個進程間的資料交換,可以使用Queue、Pipe、Manager,其中:
1)Queue \ Pipe 只是實現進程間資料的傳遞;
2)Manager 實現了進程間資料的共用,即多個進程可以修改同一份資料;
2.1 Queue
Queue允許多個進程放入,多個進程從隊列取出對象,先進先出。(使用方法跟threading裡的queue差不多)
from multiprocessing import Process,Queue def f(qq): qq.put([42,None,"hello"]) qq.put([43,None,"HI"]) if __name__ == ‘__main__‘: q = Queue() p = Process(target=f,args=(q,)) p.start() print(q.get()) print(q.get()) p.join()
2.2 Pipe
Pipe也是先進先出
from multiprocessing import Process, Pipe def f(conn): conn.send([42, None, ‘兒子發送的訊息‘]) conn.send([42, None, ‘兒子又發訊息啦‘]) print("接收父親的訊息:",conn.recv()) conn.close() if __name__ == ‘__main__‘: parent_conn, child_conn = Pipe() p = Process(target=f, args=(child_conn,)) p.start() print(parent_conn.recv()) # prints "[42, None, ‘hello‘]" print(parent_conn.recv()) # prints "[42, None, ‘hello‘]" parent_conn.send("回家吃飯!") # prints "[42, None, ‘hello‘]" p.join()
2.3 Manager
Manager對象類似於伺服器與客戶之間的通訊 (server-client),與我們在Internet上的活動很類似。我們用一個進程作為伺服器,建立Manager來真正存放資源。其它的進程可以通過參數傳遞或者根據地址來訪問Manager,建立串連後,動作伺服器上的資源。在防火牆允許的情況下,我們完全可以將Manager運用於多電腦,從而模仿了一個真實的網路情境。
from multiprocessing import Process,Manager import os def f(d,l): d[os.getpid()] = os.getpid() l.append(os.getpid()) print(l) if __name__ == "__main__": with Manager() as manager: d = manager.dict()#產生一個字典,可在多個進程間共用和傳遞 l = manager.list(range(5))#產生一個列表,可在多個進程間實現共用和傳遞 p_list = [] for i in range(10): p = Process(target=f,args=(d,l)) p.start() p_list.append(p) for res in p_list:#等待結果 res.join()
3. 進程池
進程池 (Process Pool)可以建立多個進程。這些進程就像是隨時待命計程車兵,準備執行任務(程式)。一個進程池中可以容納多個待命計程車兵。
進程池有兩種方法:
1)串列:apply
2)並行:apply_async
from multiprocessing import Process,Pool import time import os def Foo(i): time.sleep(2) print("in process",os.getpid()) return i+100 def Bar(arg): ‘‘‘回呼函數‘‘‘ print("-->>exec done:",arg,os.getpid()) if __name__ == "__main__": pool = Pool(processes=3)#允許進程池同時放入3個進程 print("主進程",os.getpid()) for i in range(10): pool.apply_async(func=Foo,args=(i,),callback=Bar) print(‘end‘) pool.close() pool.join()#進程池中進程執行完畢後在關閉;如果注釋則程式直接關閉
使用回呼函數的目的是:在父進程中執行可以提高效率;(比如串連資料庫,寫回呼函數的話,父進程串連一次資料庫即可;如果使用子進程,則需要串連多次)
4. 其他(lock)
lock:螢幕上列印的鎖,防止列印顯示混亂
from multiprocessing import Process, Lock def f(l, i): #上鎖 l.acquire() try: print(‘hello world‘, i) finally: #解鎖 l.release() #因為螢幕是共用的,定義鎖的目的是列印的資訊不換亂,而不是順序不會亂 if __name__ == ‘__main__‘: #定義鎖 lock = Lock() for num in range(10): Process(target=f, args=(lock, num)).start()
Python(8)線程、進程