進程和線程的概念、區別和聯系


進程(process)和線程(thread)是操作系統的基本概念,但是它們比較抽象,不容易掌握。

最近,我讀到一篇材料,發現有一個很好的類比,可以把它們解釋地清晰易懂。

進程與線程的一個簡單解釋

在這個簡單易懂的類比下,了解一下進程和線程的宏觀概念:

進程,是並發執行的程序在執行過程中分配和管理資源的基本單位,是一個動態概念,竟爭計算機系統資源的基本單位。每一個進程都有一個自己的地址空 間,即進程空間或(虛空間)。進程空間的大小 只與處理機的位數有關,一個 16 位長處理機的進程空間大小為 216 ,而 32 位處理機的進程空間大小為 232 。進程至少有 5 種基本狀態,它們是:初始態,執行態,等待狀態,就緒狀態,終止狀態。

線程,在網絡或多用戶環境下,一個服務器通常需要接收大量且不確定數量用戶的並發請求,為每一個請求都創建一個進程顯然是行不通的,——無論是從系統資源開銷方面或是響應用戶請求的效率方面來看。因此,操作系統中線程的概念便被引進了。線程,是進程的一部分,一個沒有線程的進程可以被看作是單線程的。線程有時又被稱為輕權進程或輕量級進程,也是 CPU 調度的一個基本單位。

說到這里,我們對進程與線程都有了一個大體上的印象,現在開始說說二者大致的區別。

    進程的執行過程是線狀的, 盡管中間會發生中斷或暫停,但該進程所擁有的資源只為該線狀執行過程服務。一旦發生進程上下文切換,這些資源都是要被保護起來的。這是進程宏觀上的執行過 程。而進程又可有單線程進程與多線程進程兩種。我們知道,進程有 一個進程控制塊 PCB ,相關程序段 和 該程序段對其進行操作的數據結構集 這三部分,單線程進程的執行過程在宏觀上是線性的,微觀上也只有單一的執行過程;而多線程進程在宏觀上的執行過程同樣為線性的,但微觀上卻可以有多個執行 操作(線程),如不同代碼片段以及相關的數據結構集。線程的改變只代表了 CPU 執行過程的改變,而沒有發生進程所擁有的資源變化。了 CPU 之外,計算機內的軟硬件資源的分配與線程無關,線程只能共享它所屬進程的資源。與進程控制表和 PCB 相似,每個線程也有自己的線程控制表 TCB ,而這個 TCB 中所保存的線程狀態信息則要比 PCB 表少得多,這些信息主要是相關指針用堆棧(系統棧和用戶棧),寄存器中的狀態數據。進程擁有一個完整的虛擬地址空間,不依賴於線程而獨立存在;反之,線程是進程的一部分,沒有自己的地址空間,與進程內的其他線程一起共享分配給該進程的所有資源

    線程可以有效地提高系統的執行效率,但並不是在所有計算機系統中都是適用的,如某些很少做進程調度和切換的實時系統。使用線程的好處是有多個任務需要處理 機處理時,減少處理機的切換時間;而且,線程的創建和結束所需要的系統開銷也比進程的創建和結束要小得多。最適用使用線程的系統是多處理機系統和網絡系統 或分布式系統。

 

線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。 

 從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。

1. 線程的執行特性。

    線程只有 3 個基本狀態:就緒,執行,阻塞。

    線程存在 5 種基本操作來切換線程的狀態:派生,阻塞,激活,調度,結束。

2. 進程通信。

    單機系統中進程通信有 4 種形式:主從式,會話式,消息或郵箱機制,共享存儲區方式。

    主從式典型例子:終端控制進程和終端進程。

    會話式典型例子:用戶進程與磁盤管理進程之間的通信。

 

關於多進程和多線程:

一.為何需要多進程(或者多線程),為何需要並發?

這個問題或許本身都不是個問題。但是對於沒有接觸過多進程編程的朋友來說,他們確實無法感受到並發的魅力以及必要性。

我想,只要你不是整天都寫那種int main()到底的代碼的人,那么或多或少你會遇到代碼響應不夠用的情況,也應該有嘗過並發編程的甜頭。就像一個快餐點的服務員,既要在前台接待客戶點 餐,又要接電話送外賣,沒有分身術肯定會忙得你焦頭爛額的。幸運的是確實有這么一種技術,讓你可以像孫悟空一樣分身,靈魂出竅,樂哉樂哉地輕松應付一切狀 況,這就是多進程/線程技術。

並發技術,就是可以讓你在同一時間同時執行多條任務的技術。你的代碼將不僅僅是從上到下,從左到右這樣規規矩矩的一條線執行。你可以一條線在main函數里跟你的客戶交流,另一條線,你早就把你外賣送到了其他客戶的手里。

所以,為何需要並發?因為我們需要更強大的功能,提供更多的服務,所以並發,必不可少。

二.多進程

什么是進程。最直觀的就是一個個pid,官方的說法就:進程是程序在計算機上的一次執行活動。

說得簡單點,下面這段代碼執行的時候

 
復制代碼
int main() { printf(”pid is %d/n”,getpid() ); return 0; } 
復制代碼

進入main函數,這就是一個進程,進程pid會打印出來,然后運行到return,該函數就退出,然后由於該函數是該進程的唯一的一次執行,所以return后,該進程也會退出。

看看多進程。linux下創建子進程的調用是fork();

復制代碼
#include <unistd.h>  
#include <sys/types.h> #include <stdio.h> void print_exit() { printf("the exit pid:%d/n",getpid() ); } main () { pid_t pid; atexit( print_exit ); //注冊該進程退出時的回調函數 pid=fork(); if (pid < 0) printf("error in fork!"); else if (pid == 0) printf("i am the child process, my process id is %d/n",getpid()); else { printf("i am the parent process, my process id is %d/n",getpid()); sleep(2); wait(); } } 
復制代碼

i am the child process, my process id is 15806
the exit pid:15806
i am the parent process, my process id is 15805
the exit pid:15805

這是gcc測試下的運行結果。

關於fork函數,功能就是產生子進程,由於前面說過,進程就是執行的流程活動。

那么fork產生子進程的表現就是它會返回2次,一次返回0,順序執行下面的代碼。這是子進程。

一次返回子進程的pid,也順序執行下面的代碼,這是父進程。

(為何父進程需要獲取子進程的pid呢?這個有很多原因,其中一個原因:看最后的wait,就知道父進程等待子進程的終結后,處理其task_struct結構,否則會產生僵屍進程,扯遠了,有興趣可以自己google)。

如果fork失敗,會返回-1.

額外說下atexit( print_exit ); 需要的參數肯定是函數的調用地址。

這里的print_exit 是函數名還是函數指針呢?答案是函數指針,函數名永遠都只是一串無用的字符串。

某本書上的規則:函數名在用於非函數調用的時候,都等效於函數指針。

 

說到子進程只是一個額外的流程,那他跟父進程的聯系和區別是什么呢?

我很想建議你看看linux內核的注解(有興趣可以看看,那里才有本質上的了解),總之,fork后,子進程會復制父進程的task_struct結構,並為子進程的堆棧分配物理頁。理論上來說,子進程應該完整地復制父進程的堆,棧以及數據空間,但是2者共享正文段。

關於寫時復制:由於一般 fork后面都接着exec,所以,現在的 fork都在用寫時復制的技術,顧名思意,就是, 數據段,堆,棧,一開始並不復制,由父,子進程共享,並將這些內存設置為只讀。直到父,子進程一方嘗試寫這些區域,則內核才為需要修改的那片內存拷貝副 本。這樣做可以提高 fork的效率。

三.多線程

線程是可執行代碼的可分派單元。這個名稱來源於“執行的線索”的概念。在基於線程的多任務的環境中,所有進程有至少一個線程,但是它們可以具有多個任務。這意味着單個程序可以並發執行兩個或者多個任務。

簡而言之,線程就是把一個進程分為很多片,每一片都可以是一個獨立的流程。這已經明顯不同於多進程了,進程是一個拷貝的 流程,而線程只是把一條河流截成很多條小溪。它沒有拷貝這些額外的開銷,但是僅僅是現存的一條河流,就被多線程技術幾乎無開銷地轉成很多條小流程,它的偉 大就在於它少之又少的系統開銷。(當然偉大的后面又引發了重入性等種種問題,這個后面慢慢比較)。

關於多線程與多進程,線程安全,函數可重入詳見http://blog.csdn.net/hairetz/article/details/4281931/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM