Python(8)線程、進程

來源:互聯網
上載者:User

標籤: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.RLockthreading.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)線程、進程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.