進程線程相關

【進程和線程的區別】

進程是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。

線程是進程的一個實體,是CPU調度和分派的基本單位,是比進程更小的能獨立運行的基本單位,只擁有在運行中必不可少的資源(如程序計數器,寄存器和棧),可與同屬一個進程的其他的線程共享進程所擁有的全部資源。對線程的調度所付出的開銷會小得多,能更高效的提高系統內多個程序間併發執行的程度。

線程是進程中的一部分,同一個進程中的多個線程之間可以併發執行。相對進程而言,線程是一個更加接近於執行體的概念,它可以與同進程中的其他線程共享數據,但擁有自己的棧空間,擁有獨立的執行序列。

進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變量的併發操作,只能用線程,不能用進程。

進程優點:編程、調試簡單,可靠性較高。
進程缺點:創建、銷燬、切換速度慢,內存、資源佔用大。
線程優點:創建、銷燬、切換速度快,內存、資源佔用小。
線程缺點:編程、調試複雜,可靠性較差。

上面的對比可以歸結爲一句話:“線程快而進程可靠性高”。線程有個別名叫“輕量級進程”,在有的書籍資料上介紹線程可以十倍、百倍的效率快於進程; 而進程之間不共享數據,沒有鎖問題,結構簡單,一個進程崩潰不像線程那樣影響全局,因此比較可靠。

【多線程&多進程】

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

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

1. 創建子進程

linux下創建子進程的調用是fork(),fork產生子進程的表現就是它會返回2次,子進程的返回值爲0,因爲子進程可以隨時調用getpid()獲取自己的pid,也可以調用getppid()獲取父進程的id;父進程的返回值爲子進程的pid,因爲進程的子進程可以多於一個,沒有一個函數使得一個進程可以獲得所有子進程的id。如果fork失敗,會返回-1。
爲何父進程需要獲取子進程的pid呢?這個有很多原因,其中一個原因:看最後的wait,就知道父進程等待子進程的終結後,處理其task_struct結構,否則會產生殭屍進程。
fork後,子進程會複製父進程的task_struct結構,併爲子進程的堆棧分配物理頁。理論上來說,子進程應該完整地複製父進程的堆,棧以及數據空間,但是2者共享正文段。
關於寫時複製:由於一般 fork後面都接着exec,所以,現在的 fork都在用寫時複製的技術,顧名思意,就是數據段,堆,棧,一開始並不複製,由父,子進程共享,並將這些內存設置爲只讀。直到父,子進程一方嘗試寫這些區域,則內核才爲需要修改的那片內存拷貝副本。這樣做可以提高 fork的效率。

線程就是把一個進程分爲很多片,每一片都可以是一個獨立的流程,系統開銷少。

多線程系統調用:


http://blog.csdn.net/hairetz/article/details/4281931/

linux下要編譯使用線程的代碼,一定要記得調用pthread庫。如下編譯:

 gcc -o pthrea -pthread  pthrea.c

【線程安全和可重入】

線程安全:概念比較直觀。一般說來,一個函數被稱爲線程安全的,當且僅當被多個併發線程反覆調用時,它會一直產生正確的結果。

線程安全的條件:
要確保函數線程安全,主要需要考慮的是線程之間的共享變量。屬於同一進程的不同線程會共享進程內存空間中的全局區和堆,而私有的線程空間則主要包括棧和寄存器。因此,對於同一進程的不同線程來說,每個線程的局部變量都是私有的,而全局變量、局部靜態變量、分配於堆的變量都是共享的。在對這些共享變量進行訪問時,如果要保證線程安全,則必須通過加鎖的方式。

可重入:概念基本沒有比較正式的完整解釋,但是它比線程安全要求更嚴格。根據經驗,所謂“重入”,常見的情況是,程序執行到某個函數foo()時,收到信號,於是暫停目前正在執行的函數,轉到信號處理函數,而這個信號處理函數的執行過程中,又恰恰也會進入到剛剛執行的函數foo(),這樣便發生了所謂的重入。此時如果foo()能夠正確的運行,而且處理完成後,之前暫停的foo()也能夠正確運行,則說明它是可重入的。

可重入的判斷條件:
要確保函數可重入,需滿足以下幾個條件:
1、不在函數內部使用靜態或全局數據 
2、不返回靜態或全局數據,所有數據都由函數的調用者提供。 
3、使用本地數據,或者通過製作全局數據的本地拷貝來保護全局數據。
4、不調用不可重入函數。

可重入與線程安全並不等同,一般說來,可重入的函數一定是線程安全的,但反過來不一定成立。


【進程/線程間同步】

併發,是在同一個cpu上同時(不是真正的同時,而是看來是同時,因爲cpu要在多個程序間切換)運行多個程序。

並行,是每個cpu運行一個程序。

同步,是協同步調,按預定的先後次序進行運行。

互斥,兩個或兩個以上的進程,不能同時進入關於同一組共享變量的臨界區域,否則可能發生與時間有關的錯誤,這種現象被稱作進程互斥.

把異步環境下的一組併發進程因直接制約而互相發送消息、進行互相合作、互相等待,使得各進程按一定的速度執行的過程稱爲進程間的同步。

1.  臨界區(Critical Section)-- 同一個進程內實現互斥

通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。在任意時刻只允許一個線程對共享資源進行訪問,如果有多個線程試圖訪問臨界區,那麼在有一個線程進入後,其他試圖訪問此臨界區的線程將被掛起,並一直等到進入臨界區的線程離開,臨界區在被釋放後,其他線程纔可以搶佔。

2. 互斥量(Mutex)-- 可以跨進程實現互斥

採用互斥對象機制。 只有擁有互斥對象的線程纔有訪問公共資源的權限,因爲互斥對象只有一個,所以能保證公共資源不會同時被多個線程訪問。當前佔據資源的線程在任務處理完後應將擁有的互斥對象交出,以便其他線程在獲得後得以訪問資源。互斥不僅能實現同一應用程序的公共資源安全共享,還能實現不同應用程序的公共資源安全共享 。

3. 信號量(Semaphore)--主要是實現同步,可以跨進程

信號量允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。一般是將當前可用資源計數設置爲最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大於0的,就可以發出信號量信號。但是當前可用計數減小到0時,則說明當前佔用資源的線程數已經達到了所允許的最大數目,不能再允許其他線程的進入,此時的信號量信號將無法發出。

PV操作及信號量的概念都是由荷蘭科學家E.W.Dijkstra提出的。信號量S是一個整數,S大於等於零時代表可供併發進程使用的資源實體數,但S小於零時則表示正在等待使用共享資源的進程數。

P操作申請資源:

(1)S減1;

(2)若S減1後仍大於等於零,則進程繼續執行;

(3)若S減1後小於零,則該進程被阻塞後進入與該信號相對應的隊列中,然後轉入進程調度。  

V操作釋放資源:

(1)S加1;

(2)若相加結果大於零,則進程繼續執行;

(3)若相加結果小於等於零,則從該信號的等待隊列中喚醒一個等待進程,然後再返回原進程繼續執行或轉入進程調度。

4. 事件(Event)-- 實現同步,可以跨進程

用來通知線程有一些事件已發生,從而啓動後繼任務的開始。

 

總結:

1.互斥量與臨界區的作用非常相似,但互斥量是可以命名的,也就是說它可以跨越進程使用。互斥量比臨界區複雜。因爲使用互斥不僅僅能夠在同一應用程序不同線程中實現資源的安全共享,而且可以在不同應用程序的線程之間實現對資源的安全共享。所以創建互斥量需要的資源更多,所以如果只爲了在進程內部是用的話使用臨界區會帶來速度上的優勢並能夠減少資源佔用量。因爲互斥量是跨進程的互斥量一旦被創建,就可以通過名字打開它。

2. 通過互斥量可以指定資源被獨佔的方式使用,但如果有下面一種情況通過互斥量就無法處理,比如現在一位用戶購買了一份三個併發訪問許可的數據庫系統,可以根據用戶購買的訪問許可數量來決定有多少個線程/進程能同時進行數據庫操作,這時候如果利用互斥量就沒有辦法完成這個要求,信號燈對象可以說是一種資源計數器。

【進程間通信方式】

進程間通信就是在不同進程之間傳播或交換信息。

進程的用戶空間是互相獨立的,一般而言是不能互相訪問的,唯一的例外是共享內存區。但是,系統空間卻是“公共場所”,所以內核顯然可以提供這樣的條件。除此以外,那就是雙方都可以訪問的外設了。在這個意義上,兩個進程當然也可以通過磁盤上的普通文件交換信息,或者通過“註冊表”或其它數據庫中的某些表項和記錄交換信息。廣義上這也是進程間通信的手段,但是一般都不把這算作“進程間通信”。因爲那些通信手段的效率太低了,而人們對進程間通信的要求是要有一定的實時性。

進程間通信主要包括管道,系統IPC(包括消息隊列,信號量,共享內存),SOCKET.

1. 管道

管道是一種半雙工的通信方式,數據只能單向流動,一端讀一端取,當一端分配到讀任務後,那麼他就固定了,不能再擔當寫的角色了。

管道只能承載無格式字節流,緩衝區大小受限。管道的大小,即爲sizeof(readbuffer),寫入比該數字大的數據時,先只會寫sizeof(readbuffer)個數據到管道,然後寫端阻塞,等待讀端取走數據,然後按同樣的規則寫入剩餘的部分,這個也體現了寫入操作的非原子性。寫請求字節數還有一個最大閥值,在/usr/include/linux有文件 limits.h中宏定義
            #define PATH_MAX        4096
            此時,假如指明的管道大小大於PATH_MAX ,系統會按這個PATH_MAX 作爲寫請求最大字節數。

管道分爲命名管道和匿名管道,匿名管道只能用於父子進程之間的通信,而命名管道則可用於非父子進程之間。命名管道就是FIFO,管道是先進先出的通訊方式 。

2. 消息隊列

消息隊列用於運行於同一臺機器上的進程間通信,與管道相似;首先在一個進程中創建一個消息隊列,然後再往消息隊列中寫數據,而另一個進程則從那個消息隊列中取數據。

需要注意的是,消息隊列是用創建文件的方式建立的,如果一個進程向某個消息隊列中寫入了數據之後,另一個進程並沒有取出數據,即使向消息隊列中寫數據的進程已經結束,保存在消息隊列中的數據並沒有消失,也就是說下次再從這個消息隊列讀數據的時候,就是上次的數據!!!!  

消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。

3. 信號量

本質上,信號量是一個計數器,它用來控制多個進程對共享資源的訪問。它常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。一般說來,爲了獲得共享資源,進程需要執行下列操作:

(1)測試控制該資源的信號量;

(2)若此信號量的值爲正,則允許進程使用該資源,進程將進號量減1;

(3)若此信號量爲0,則該資源目前不可用,進程進入睡眠狀態,直至信號量值大於0,進程被喚醒,轉入步驟(1);

(4)當進程不再使用一個信號量控制的資源時,信號量值加1,如果此時有進程正在睡眠等待此信號量,則喚醒此進程。

4. 共享內存

共享內存通常由一個進程創建,其餘進程對這塊內存區進行讀寫。

共享內存是最快的 IPC 方式,往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。

得到共享內存有兩種方式:映射/dev/mem設備和內存映像文件。前一種方式不給系統帶來額外的開銷,但在現實中並不常用,因爲它控制存取的是實際的物理內存;  

5. 套接字

套接字通信並不爲Linux所專有,在所有提供了TCP/IP協議棧的操作系統中幾乎都提供了socket,而所有這樣操作系統,對套接字的編程方法幾乎是完全一樣的

6. 信號

信號是一種比較複雜的通信方式,用於通知接收進程某個事件已經發生。

信號可以在任何時候發送給某一進程,而無須知道該進程的狀態。如果該進程並未處於執行狀態,則該信號就由內核保存起來,直到該進程恢復執行並傳遞給他爲止。如果一個信號被進程設置爲阻塞,則該信號的傳遞被延遲,直到其阻塞被取消時才被傳遞給進程。

以上幾種方式的比較:    

1. 匿名管道:速度慢,容量有限,只有父子進程能通訊    

2. FIFO:任何進程間都能通訊,但速度慢    

3. 消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題    

4. 信號量:不能傳遞複雜消息,只能用來同步    

5.共享內存區:能夠很容易控制容量,速度快,但要保持同步,比如一個進程在寫的時候,另一個進程要注意讀寫的問題,相當於線程中的線程安全,當然,共享內存區同樣可以用作線程間通訊,不過沒這個必要,線程間本來就已經共享了同一進程內的一塊內存

 


 【參考】

1. http://blog.csdn.net/yaosiming2011/article/details/44280797

2. http://blog.csdn.net/hairetz/article/details/4281931/

3. http://blog.csdn.net/yang1994/article/details/7732116

4. http://www.cnblogs.com/LUO77/p/5816326.html

發佈了41 篇原創文章 · 獲贊 41 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章