當前位置:
首頁 > 最新 > 為你詳細解析Python中的線程與進程的區別

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

一、什麼是進程/線程

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


二、進程

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


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

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


一個進程由一個或者多個線程組成,線程是一個進程中代碼的不同執行路線。

切換進程需要的資源比切換線程的要多的多。

進程之間相互獨立,而同一個進程下的線程共享程序的內存空間(如代碼段,數據集,堆棧等)。某進程內的線程在其他進程不可見。換言之,線程共享同一片內存空間,而進程各有獨立的內存空間。

以下是作者在知乎上看到的關於進程與線程的討論,其中一個我感覺很有道理,摘抄如下:

作者:zhonyong

鏈接:https://www.zhihu.com/question/25532384/answer/81152571

首先來一句概括的總論:進程和線程都是一個時間段的描述,是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爬蟲開發與項目實踐》時,執行下面一段代碼:

顯示的結果是

可以看到,程序在執行完

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


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

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

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

七、進程間的通信

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

Queue是一種多進程安全的隊列。實現多進程間的通信有兩種方法:

- get() 用於向隊列中加入數據。有兩個屬性:blocked和timeout。blocked為true時(默認為True)且timeout為正值時,如果當隊列已滿會阻塞timeout時間,在這個時間內如果隊列有空位會加入,如果超過時間仍然沒有空位會拋出Queue.Full異常。

- put() 用於從隊列中獲取一個數據並將其從隊列中刪除。有兩個屬性:blocked和timeout。blocked為true(默認為True)且timeout為正值時,如果當前隊列為空會阻塞timeout時間,在這個時間內如果隊列有新數據會獲取,如果超過時間仍然沒有新數據會拋出Queue.Empty異常。

我們來看一下運行結果:


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()會一直阻塞直到管道關閉(任意一端進程接結束則管道關閉)。

讓我們看一下輸出結果


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


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

等待(阻塞)

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

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

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


鎖的用處:

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

鎖的壞處:

1. 阻止了多線程並發執行,效率大大降低。

2. 由於存在多個鎖,不同的線程持有不同的鎖並試圖獲取對方的鎖時,可能造成死鎖。


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

喜歡記得來一個


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python 的精彩文章:

厲害了,我的Python!
我們從不販賣焦慮,我們只「販賣」Python

TAG:Python |