詳細解析Python中的線程與進程的區別

来源:https://www.cnblogs.com/pythonedu/archive/2018/05/26/9093117.html
-Advertisement-
Play Games

什麼是進程/線程 眾所周知,CPU是電腦的核心,它承擔了所有的計算任務。而操作系統是電腦的管理者,是一個大管家,它負責任務的調度,資源的分配和管理,統領整個電腦硬體。應用程式是具有某種功能的程式,程式運行與操作系統之上。 進程 進程時一個具有一定功能的程式在一個數據集上的一次動態執行過程。進程 ...


什麼是進程/線程

眾所周知,CPU是電腦的核心,它承擔了所有的計算任務。而操作系統是電腦的管理者,是一個大管家,它負責任務的調度,資源的分配和管理,統領整個電腦硬體。應用程式是具有某種功能的程式,程式運行與操作系統之上。

進程

進程時一個具有一定功能的程式在一個數據集上的一次動態執行過程。進程由程式,數據集合和進程式控制制塊三部分組成。程式用於描述進程要完成的功能,是控制進程執行的指令集;數據集合是程式在執行時需要的數據和工作區;程式控制塊(PCB)包含程式的描述信息和控制信息,是進程存在的唯一標誌。

線程

在很早的時候電腦並沒有線程這個概念,但是隨著時代的發展,只用進程來處理程式出現很多的不足。如當一個進程堵塞時,整個程式會停止在堵塞處,並且如果頻繁的切換進程,會浪費系統資源。所以線程出現了。

線程是能擁有資源和獨立運行的最小單位,也是程式執行的最小單位。一個進程可以擁有多個線程,而且屬於同一個進程的多個線程間會共用該進行的資源。

進程與線程的區別

  1. 一個進程由一個或者多個線程組成,線程是一個進程中代碼的不同執行路線。
  2. 切換進程需要的資源比切換線程的要多的多。
  3. 進程之間相互獨立,而同一個進程下的線程共用程式的記憶體空間(如代碼段,數據集,堆棧等)。某進程內的線程在其他進程不可見。換言之,線程共用同一片記憶體空間,而進程各有獨立的記憶體空間。
    以下是作者在知乎上看到的關於進程與線程的討論,其中一個我感覺很有道理,摘抄如下:
    作者:zhonyong
    鏈接:
首先來一句概括的總論:進程和線程都是一個時間段的描述,是CPU工作時間段的描述。下麵細說背景:CPU+RAM+各種資源(比如顯卡,光碟機,鍵盤,GPS, 等等外設)構成我們的電腦,但是電腦的運行,實際就是CPU和相關寄存器以及RAM之間的事情。一個最最基礎的事實:CPU太快,太快,太快了,寄存器僅僅能夠追的上他的腳步,RAM和別的掛在各匯流排上的設備完全是望其項背。那當多個任務要執行的時候怎麼辦呢?輪流著來?或者誰優先順序高誰來?不管怎麼樣的策略,一句話就是在CPU看來就是輪流著來。一個必須知道的事實:執行一段程式代碼,實現一個功能的過程介紹 ,當得到CPU的時候,相關的資源必須也已經就位,就是顯卡啊,GPS啊什麼的必須就位,然後CPU開始執行。這裡除了CPU以外所有的就構成了這個程式的執行環境,也就是我們所定義的程式上下文。當這個程式執行完了,或者分配給他的CPU執行時間用完了,那它就要被切換出去,等待下一次CPU的臨幸。在被切換出去的最後一步工作就是保存程式上下文,因為這個是下次他被CPU臨幸的運行環境,必須保存。串聯起來的事實:前面講過在CPU看來所有的任務都是一個一個的輪流執行的,具體的輪流方法就是:先載入程式A的上下文,然後開始執行A,保存程式A的上下文,調入下一個要執行的程式B的程式上下文,然後開始執行B,保存程式B的上下文。。。。

========= 重要的東西出現了========

進程和線程就是這樣的背景出來的,兩個名詞不過是對應的CPU時間段的描述,名詞就是這樣的功能。進程就是包換上下文切換的程式執行時間總和 = CPU載入上下文+CPU執行+CPU保存上下文線程是什麼呢?進程的顆粒度太大,每次都要有上下的調入,保存,調出。如果我們把進程比喻為一個運行在電腦上的軟體,那麼一個軟體的執行不可能是一條邏輯執行的,必定有多個分支和多個程式段,就好比要實現程式A,實際分成 a,b,c等多個塊組合而成。那麼這裡具體的執行就可能變成:程式A得到CPU =》CPU載入上下文,開始執行程式A的a小段,然後執行A的b小段,然後再執行A的c小段,最後CPU保存A的上下文。這裡a,b,c的執行是共用了A的上下文,CPU在執行的時候沒有進行上下文切換的。這裡的a,b,c就是線程,也就是說線程是共用了進程的上下文環境,的更為細小的CPU時間段。到此全文結束,再一個總結:進程和線程都是一個時間段的描述,是CPU工作時間段的描述,不過是顆粒大小不同。

 

開進程需要時間

學習《python爬蟲開發與項目實踐》時,執行下麵一段代碼:

from multiprocessing import Process
import os

def run_process(name):
    print("Child process %s (%s) is running" % (name,os.getpid()))

if __name__ == "__main__":
    print("parant process %s " % os.getpid())
    for i in range(5):
        p = Process(target=run_process, args=(str(i),))
        print("process will start")
        p.start()
    p.join()
    print("process end")  

 

顯示的結果是

parant process 6332 
process will start
process will start
process will start
process will start
process will start
Child process 2 (9896) is running
Child process 0 (11208) is running
Child process 3 (5464) is running
Child process 1 (10208) is running
Child process 4 (12596) is running
process end

 

可以看到,程式在執行完

print ("parant process %s " % os.getpid())

 

沒有接著馬上執行run_process(),而是先列印process will start,最後把子進程一起執行。這是因為子進程的創建是需要時間的,在這個空閑時間里父進程繼續執行代碼,而子進程在創建完成後顯示。

Pool進程池

需要創建多個進程時,可以使用multiprocessing中的Pool類開進程池。Pool()預設開啟數量等於當前cpu核心數的子進程(當然可以手動改變)

from multiprocessing import Pool

def hello(i):
    print("hello ,this is the %d process" % i)

def main():
    p = Pool()
    for i in range(1,5):
        p.apply_async(target=hell0,args=(i,))
    p.close    
    p.join

if __name__ == "__main__":
    main()

 

apply_async表示在開進程時不阻塞主進程,是非同步IO的一種方式之一。targe參數傳入要在子線程中執行的函數對象,args以元組的方式傳入函數的參數。

join會等待線程池中的每一個線程執行完畢,在調用join之前必須要先調用close,close表示不能再向線程池中添加新的process了。

進程間的通信

每個進程各自有不同的用戶地址空間,任何一個進程的全局變數在另一個進程中都看不到,所以進程之間要交換數據必須通過內核,在內核中開闢一塊緩衝區,進程A把數據從用戶空間拷到內核緩衝區,進程B再從內核緩衝區把數據讀走,內核提供的這種機制稱為進程間通信。假如創建了多個進程,那麼進程間的通信是必不可少的。Python提供了多種進程通信的方式,其中以Queue和Pipe用得最多。下麵分別介紹這兩種模式。

Queue

Queue是一種多進程安全的隊列。實現多進程間的通信有兩種方法:
- get() 用於向隊列中加入數據。有兩個屬性:blocked和timeout。blocked為true時(預設為True)且timeout為正值時,如果當隊列已滿會阻塞timeout時間,在這個時間內如果隊列有空位會加入,如果超過時間仍然沒有空位會拋出Queue.Full異常。
- put() 用於從隊列中獲取一個數據並將其從隊列中刪除。有兩個屬性:blocked和timeout。blocked為true(預設為True)且timeout為正值時,如果當前隊列為空會阻塞timeout時間,在這個時間內如果隊列有新數據會獲取,如果超過時間仍然沒有新數據會拋出Queue.Empty異常。

from multiprocessing import Process,Queue
import os

def put_data(q,nums):
    print('現在的進程編號為:%s,這是一個put進程' % os.getpid())
    for num in nums:
        q.put(num)
        print('%d已經放入隊列中啦!' % num)

def get_data(q):
    print('現在的進程編號為:%s,這是一個get進程' % os.getpid())
    while True:
        print('已經從隊列中獲取%s並從中刪除' % q.get())

if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=put_data,args=(q,['1','2','3'],))
    p2 = Process(target=put_data,args=(q,['4','5','6'],))
    p3 = Process(target=get_data,args=(q,))
    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    # p3是個死迴圈,需要手動結束這個進程
    p3.terminate()

 

我們來看一下運行結果:

現在的進程編號為:10336,這是一個put進程
1已經放入隊列中啦!
2已經放入隊列中啦!
3已經放入隊列中啦!
現在的進程編號為:9116,這是一個get進程
已經從隊列中獲取1,並從中刪除
已經從隊列中獲取2並從中刪除
已經從隊列中獲取3並從中刪除
現在的進程編號為:2732,這是一個put進程
4已經放入隊列中啦!
5已經放入隊列中啦!
已經從隊列中獲取4,並從中刪除
6已經放入隊列中啦!
已經從隊列中獲取5並從中刪除
已經從隊列中獲取6並從中刪除

 

Pipe

Pipe與Queue不同之處在於Pipe是用於兩個進程之間的通信。就像進程位於一根水管的兩端。讓我們看看Pipe官方文檔的描述:

Returns a pair (conn1, conn2) of Connection objects representing the ends of a pipe.

Piep返回conn1和conn2代表水管的兩端。Pipe還有一個參數duplex(adj. 二倍的,雙重的 n. 雙工;占兩層樓的公寓套房),預設為True。當duplex為True時,開啟雙工模式,此時水管的兩邊都可以進行收發。當duplex為False,那麼conn1只負責接受信息,conn2只負責發送信息。
conn通過send()和recv()來發送和接受信息。值得註意的是,如果管道中沒有信息可接受,recv()會一直阻塞直到管道關閉(任意一端進程接結束則管道關閉)。

from multiprocessing import Process,Pipe
import os

def put_data(p,nums):
    print('現在的進程編號為:%s,這個一個send進程' % os.getpid())
    for num in nums:
        p.send(num)
        print('%s已經放入管道中啦!' % num)

def get_data(p):
    print('現在的進程編號為:%s,這個一個recv進程' % os.getpid())
    while True:
        print('已經從管道中獲取%s並從中刪除' % p.recv())

if __name__ == '__main__':
    p = Pipe(duplex=False)
    # 此時Pipe[1]即是Pipe返回的conn2
    p1 = Process(target=put_data,args=(p[1],['1','2','3'],))
    # 此時Pipe[0]即是Pipe返回的conn1
    p3 = Process(target=get_data,args=(p[0],))
    p1.start()
    p3.start()
    p1.join()
    p3.terminate()

 

讓我們看一下輸出結果

現在的進程編號為:9868,這個一個recv進程
現在的進程編號為:9072,這個一個send進程
1已經放入管道中啦!
已經從管道中獲取1,並從中刪除
2已經放入管道中啦!
已經從管道中獲取2並從中刪除
3已經放入管道中啦!
已經從管道中獲取3並從中刪除

 

控制線程

我們是沒有辦法完全人為控制線程的,因為線程由系統控制。但是可以用一些方式來影響線程的調用,比如互斥鎖,sleep(阻塞),死鎖等。

線程的幾種狀態

 

新建-----就緒------------------運行-----死亡

等待(阻塞)

線程的生命周期由run方法決定,當run方法結束時線程死亡。可以通過繼承Thread,重寫run方法改變Thread的功能,最後還是通過start()方法開線程。

from threading import Thread

class MyThread(Thread):
    def run(self):
        print('i am sorry')

if __name__ == '__main__':
    t = MyThread()
    t.start()

 

 

通過args參數以一個元組的方式給線程中的函數傳參。

from threading import Thread

def sorry(name):
    print('i am sorry',name)

if __name__ == '__main__':  
    t = Thread(target=sorry,args=('mike'))
    t.start()

 

線程鎖

多線程中任務中,可能會發生多個線程同時對一個公共資源(如全局變數)進行操作的情況,這是就會發生混亂。為了避免這種情況,需要引入線程鎖的概念。只有一個線程能處於上鎖狀態,當一個線程上鎖之後,如果有另外一個線程試圖獲得鎖,該線程就會掛起直到擁有鎖的線程將鎖釋放。這樣就保證了同時只有一個線程對公共資源進行訪問或修改。

from threading import Thread,Lock

num = 0
def puls():
    # 獲得一個鎖
    lock = Lock()
    global num
    # acquire()方法上鎖
    lock.acquire()
    num += 1
    print(num)
    # release()方法解鎖
    lock.release()

if __name__ == '__main__':
    for i in range(5):
        t = Thread(target=plus)
        t.start()
    t.join()     

 

join()方法會阻塞主線程直到子線程全部結束(也就是同步)。

鎖的用處:
1. 確保某段關鍵代碼只能由一個線程從頭到尾執行,保證了數據的唯一性。

鎖的壞處:

1. 阻止了多線程併發執行,效率大大降低。
2. 由於存在多個鎖,不同的線程持有不同的鎖並試圖獲取對方的鎖時,可能造成死鎖。

守護線程

線程其實並沒有主次的概念,我們一般說的‘主線程’實際上是main函數的線程,而所謂主線程結束子線程也會結束是因為在主線程結束時調用了系統的退出函數。而守護線程是指‘不重要線程’。主線程會等所有‘重要’線程結束後才結束。通常當客戶端訪問伺服器時會為這次訪問開啟一個守護線程。將setDaemon屬性設為True即可將該線程設為守護線程。

from threading import Thread

n = 100

def count(x,y):
    return n=x+y

if __name__ == '__main__':

    t = Thread(target=count,args=(1,2))
    t.setDaemon = True
    # ...
 

python學習交流群:125240963

轉載至:Python中的線程與進程


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法,對於任意一個對象,都能夠調用它的任意一個方法。這種動態獲取的以及動態調用對象的方法的功能稱為java語言的反射機制。 簡單來說, 就可以把.class文件比做動物的屍體, 而反射技術就是對屍體的一種解剖.通過反射技術, ...
  • 需要先增加一個自定義的Filter去繼承 UsernamePasswordAuthenticationFilter 或者 AbstractAuthenticationProcessingFilter 然後在自定義的Filter裡面指定登錄的Url . 設置過濾器的時候,必須為過濾器指定一個 auth ...
  • GIL(全局解釋器鎖) 每個線程在執行的過程都需要先獲取GIL 作用:在單核的情況下實現多任務(多線程),保證同一時刻只有一個線程可以執行代碼,因此造成了我們使用多線程的時候無法實現並行 多核的情況下產生gil問題 因為一個進程中有一個gil鎖,在這進程裡面的線程去搶這把鎖,在同一時刻只有一個線程能 ...
  • 內建的 datetime 模塊 讓我們看看其他庫是如何處理這種轉換的。 Dateutil 通過 Arrow datetime 類的實例,你可以訪問 Arrow 的其他有用方法。例如,它的humanize()方法將日期時間翻譯成人類可讀的短語,就像這樣: 由於 Maya 與 datetime 庫中很多 ...
  • 1. 學習計劃 第一天 1、SpringMVC介紹 2、入門程式 3、SpringMVC架構講解 a) 框架結構 b) 組件說明 4、SpringMVC整合MyBatis 5、參數綁定 a) SpringMVC預設支持的類型 b) 簡單數據類型 c) Pojo類型 d) Pojo包裝類型 e) 自定 ...
  • 在Spring Cloud封裝的Feign中並不直接支持傳文件,但可以通過引入Feign的擴展包來實現,本來就來具體說說如何實現。 原文:http://blog.didispace.com/spring cloud starter dalston 2 4/ 服務提供方(接收文件) 服務提供方的實現比 ...
  • 目錄 1. 什麼是正則化? 2. 正則化如何減少過擬合? 3. 深度學習中的各種正則化技術: L2和L1正則化 Dropout 數據增強(Data augmentation) 提前停止(Early stopping) 4. 案例:在MNIST數據集上使用Keras的案例研究 1. 什麼是正則化? 在 ...
  • 一、單個參數: 二、多參數: 三、Map封裝多參數: 四、List封裝in: 五、多參數傳遞之註解方式示: 六、selectList()只能傳遞一個參數,但實際所需參數既要包含String類型,又要包含List類型時的處理方法: 將參數放入Map,再取出Map中的List遍歷。如下: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...