這篇文章主要介紹“Linux進程與線程的區(qū)別”,在日常操作中,相信很多人在Linux進程與線程的區(qū)別問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Linux進程與線程的區(qū)別”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
嵐皋網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設計、網(wǎng)站建設、微信開發(fā)、APP開發(fā)、成都響應式網(wǎng)站建設等網(wǎng)站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯(lián)從2013年創(chuàng)立到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設就選創(chuàng)新互聯(lián)。
0.首先,簡要了解一下進程和線程。對于操作系統(tǒng)而言,進程是核心之核心,整個現(xiàn)代操作系統(tǒng)的根本,就是以進程為單位在執(zhí)行任務。系統(tǒng)的管理架構也是基于進程層面的。在按下電源鍵之后,計算機就開始了復雜的啟動過程,此處有一個經(jīng)典問題:當按下電源鍵之后,計算機如何把自己由靜止啟動起來的?本文不討論系統(tǒng)啟動過程,請讀者自行科普。操作系統(tǒng)啟動的過程簡直可以描述為上帝創(chuàng)造萬物的過程,期初沒有世界,但是有上帝,是上帝創(chuàng)造了世界,之后創(chuàng)造了萬物,然后再創(chuàng)造了人,然后塑造了人的七情六欲,再然后人類社會開始遵循自然規(guī)律繁衍生息。。。操作系統(tǒng)啟動進程的階段就相當于上帝造人的階段。本文討論的全部內(nèi)容都是“上帝造人”之后的事情。第一個被創(chuàng)造出來的進程是號進程,這個進程在操作系統(tǒng)層面是不可見的,但它存在著。號進程完成了操作系統(tǒng)的功能加載與初期設定,然后它創(chuàng)造了1號進程(init),這個1號進程就是操作系統(tǒng)的“耶穌”。1號進程是上帝派來管理整個操作系統(tǒng)的,所以在用pstree查看進程樹可知,1號進程位于樹根。再之后,系統(tǒng)的很多管理程序都以進程身份被1號進程創(chuàng)造出來,還創(chuàng)造了與人類溝通的橋梁——shell。從那之后,人類可以跟操作系統(tǒng)進行交流,可以編寫程序,可以執(zhí)行任務。。。
而這一切,都是基于進程的。每一個任務(進程)被創(chuàng)建時,系統(tǒng)會為他分配存儲空間等必要資源,然后在內(nèi)核管理區(qū)為該進程創(chuàng)建管理節(jié)點,以便后來控制和調度該任務的執(zhí)行。
進程真正進入執(zhí)行階段,還需要獲得CPU的使用權,這一切都是操作系統(tǒng)掌管著,也就是所謂的調度,在各種條件滿足(資源與CPU使用權均獲得)的情況下,啟動進程的執(zhí)行過程。
除CPU而外,一個很重要的資源就是存儲器了,系統(tǒng)會為每個進程分配獨有的存儲空間,當然包括它特別需要的別的資源,比如寫入時外部設備是可使用狀態(tài)等等。有了上面的引入,我們可以對進程做一個簡要的總結:
進程,是計算機中的程序關于某數(shù)據(jù)集合上的一次運行活動,是系統(tǒng)進行資源分配和調度的基本單位,是操作系統(tǒng)結構的基礎。它的執(zhí)行需要系統(tǒng)分配資源創(chuàng)建實體之后,才能進行。
隨著技術發(fā)展,在執(zhí)行一些細小任務時,本身無需分配單獨資源時(多個任務共享同一組資源即可,比如所有子進程共享父進程的資源),進程的實現(xiàn)機制依然會繁瑣的將資源分割,這樣造成浪費,而且還消耗時間。后來就有了專門的多任務技術被創(chuàng)造出來——線程。
線程的特點就是在不需要獨立資源的情況下就可以運行。如此一來會極大節(jié)省資源開銷,以及處理時間。
1.好了,前面的一段文字是簡要引入兩個名詞,即進程和線程。本文討論目標是解釋清楚進程和線程的區(qū)別,關于二者的技術實現(xiàn),請讀者查閱相關資料。
下面我們開始重點討論本文核心了。從下面幾個方面闡述進程和線程的區(qū)別。
1).二者的相同點
2).實現(xiàn)方式的差異
3).多任務程序設計模式的區(qū)別
4).實體間(進程間,線程間,進線程間)通信方式的不同
5).控制方式的異同
6).資源管理方式的異同
7).個體間輩分關系的迥異
8).進程池與線程池的技術實現(xiàn)差別
接下來我們就逐個進行解釋。
1).二者的相同點
無論是進程還是線程,對于程序員而言,都是用來實現(xiàn)多任務并發(fā)的技術手段。二者都可以獨立調度,因此在多任務環(huán)境下,功能上并無差異。并且二者都具有各自的實體,是系統(tǒng)獨立管理的對象個體。所以在系統(tǒng)層面,都可以通過技術手段實現(xiàn)二者的控制。而且二者所具有的狀態(tài)都非常相似。而且,在多任務程序中,子進程(子線程)的調度一般與父進程(父線程)平等競爭。
其實在Linux內(nèi)核2.4版以前,線程的實現(xiàn)和管理方式就是完全按照進程方式實現(xiàn)的。在2.6版內(nèi)核以后才有了單獨的線程實現(xiàn)。
2).實現(xiàn)方式的差異
進程是資源分配的基本單位,線程是調度的基本單位。
這句經(jīng)典名言已流傳數(shù)十年,各種操作系統(tǒng)教材都可見此描述。確實如此,這就是二者的顯著區(qū)別。讀者請注意“基本”二字。相信有讀者看到前半句的時候就在心里思考,“進程豈不是不能調度?”,非也!進程和線程都可以被調度,否則多進程程序該如何運行呢!
只是,線程是更小的可以調度的單位,也就是說,只要達到線程的水平就可以被調度了,進程自然可以被調度。它強調的是分配資源時的對象必須是進程,不會給一個線程單獨分配系統(tǒng)管理的資源。若要運行一個任務,想要獲得資源,最起碼得有進程,其他子任務可以以線程身份運行,資源共享就行了。
簡而言之,進程的個體間是完全獨立的,而線程間是彼此依存的。多進程環(huán)境中,任何一個進程的終止,不會影響到其他進程。而多線程環(huán)境中,父線程終止,全部子線程被迫終止(沒有了資源)。而任何一個子線程終止一般不會影響其他線程,除非子線程執(zhí)行了exit()系統(tǒng)調用。任何一個子線程執(zhí)行exit(),全部線程同時滅亡。
其實,也沒有人寫出只有線程而沒有進程的程序。多線程程序中至少有一個主線程,而這個主線程其實就是有main函數(shù)的進程。它是整個程序的進程,所有線程都是它的子線程。我們通常把具有多線程的主進程稱之為主線程。
從系統(tǒng)實現(xiàn)角度講,進程的實現(xiàn)是調用fork系統(tǒng)調用:
pid_t fork(void);
線程的實現(xiàn)是調用clone系統(tǒng)調用:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...
/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */
);
其中,fork()是將父進程的全部資源復制給了子進程。而線程的clone只是復制了一小部分必要的資源。在調用clone時可以通過參數(shù)控制要復制的對象。可以說,fork實現(xiàn)的是clone的加強完整版。當然,后來操作系統(tǒng)還進一步優(yōu)化fork實現(xiàn)——寫時復制技術。在子進程需要復制資源(比如子進程執(zhí)行寫入動作更改父進程內(nèi)存空間)時才復制,否則創(chuàng)建子進程時先不復制。
實際中,編寫多進程程序時采用fork創(chuàng)建子進程實體。而創(chuàng)建線程時并不采用clone系統(tǒng)調用,而是采用線程庫函數(shù)。常用線程庫有Linux-Native線程庫和POSIX線程庫。其中應用最為廣泛的是POSIX線程庫。因此讀者在多線程程序中看到的是pthread_create而非clone。
我們知道,庫是建立在操作系統(tǒng)層面上的功能集合,因而它的功能都是操作系統(tǒng)提供的。由此可知,線程庫的內(nèi)部很可能實現(xiàn)了clone的調用。不管是進程還是線程的實體,都是操作系統(tǒng)上運行的實體。
最后,我們說一下vfork() 。這也是一個系統(tǒng)調用,用來創(chuàng)建一個新的進程。它創(chuàng)建的進程并不復制父進程的資源空間,而是共享,也就說實際上vfork實現(xiàn)的是一個接近線程的實體,只是以進程方式來管理它。并且,vfork()的子進程與父進程的運行時間是確定的:子進程“結束”后父進程才運行。請讀者注意“結束”二字。并非子進程完成退出之意,而是子進程返回時。一般采用vfork()的子進程,都會緊接著執(zhí)行execv啟動一個全新的進程,該進程的進程空間與父進程完全獨立不相干,所以不需要復制父進程資源空間。此時,execv返回時父進程就認為子進程“結束”了,自己開始運行。實際上子進程繼續(xù)在一個完全獨立的空間運行著。舉個例子,比如在一個聊天程序中,彈出了一個視頻播放器。你說視頻播放器要繼承你的聊天程序的進程空間的資源干嘛?莫非視頻播放器想要窺探你的聊天隱私不成?懂了吧!
3).多任務程序設計模式的區(qū)別
由于進程間是獨立的,所以在設計多進程程序時,需要做到資源獨立管理時就有了天然優(yōu)勢,而線程就顯得麻煩多了。比如多任務的TCP程序的服務端,父進程執(zhí)行accept()一個客戶端連接請求之后會返回一個新建立的連接的描述符DES,此時如果fork()一個子進程,將DES帶入到子進程空間去處理該連接的請求,父進程繼續(xù)accept等待別的客戶端連接請求,這樣設計非常簡練,而且父進程可以用同一變量(val)保存accept()的返回值,因為子進程會復制val到自己空間,父進程再覆蓋此前的值不影響子進程工作。但是如果換成多線程,父線程就不能復用一個變量val多次執(zhí)行accept()了。因為子線程沒有復制val的存儲空間,而是使用父線程的,如果子線程在讀取val時父線程接受了另一個客戶端請求覆蓋了該值,則子線程無法繼續(xù)處理上一次的連接任務了。改進的辦法是子線程立馬復制val的值在自己的棧區(qū),但父線程必須保證子線程復制動作完成之后再執(zhí)行新的accept()。但這執(zhí)行起來并不簡單,因為子線程與父線程的調度是獨立的,父線程無法知道子線程何時復制完畢。這又得發(fā)生線程間通信,子線程復制完成后主動通知父線程。這樣一來父線程的處理動作必然不能連貫,比起多進程環(huán)境,父線程顯得效率有所下降。
PS:這里引述一個知名的面試問題:多進程的TCP服務端,能否互換fork()與accept()的位置?請讀者自行思考。
關于資源不獨立,看似是個缺點,但在有的情況下就成了優(yōu)點。多進程環(huán)境間完全獨立,要實現(xiàn)通信的話就得采用進程間的通信方式,它們通常都是耗時間的。而線程則不用任何手段數(shù)據(jù)就是共享的。當然多個子線程在同時執(zhí)行寫入操作時需要實現(xiàn)互斥,否則數(shù)據(jù)就寫“臟”了。
4).實體間(進程間,線程間,進線程間)通信方式的不同
進程間的通信方式有這樣幾種:
A.共享內(nèi)存 B.消息隊列 C.信號量 D.有名管道 E.無名管道 F.信號
G.文件 H.socket
線程間的通信方式上述進程間的方式都可沿用,且還有自己獨特的幾種:
A.互斥量 B.自旋鎖 C.條件變量 D.讀寫鎖 E.線程信號
G.全局變量
值得注意的是,線程間通信用的信號不能采用進程間的信號,因為信號是基于進程為單位的,而線程是共屬于同一進程空間的。故而要采用線程信號。
綜上,進程間通信手段有8種。線程間通信手段有13種。
而且,進程間采用的通信方式要么需要切換內(nèi)核上下文,要么要與外設訪問(有名管道,文件)。所以速度會比較慢。而線程采用自己特有的通信方式的話,基本都在自己的進程空間內(nèi)完成,不存在切換,所以通信速度會較快。也就是說,進程間與線程間分別采用的通信方式,除了種類的區(qū)別外,還有速度上的區(qū)別。
另外,進程與線程之間穿插通信的方式,除信號以外其他進程間通信方式都可采用。
線程有內(nèi)核態(tài)線程與用戶級線程,相關知識請參看我的另一篇博文《Linux線程的實質》。
5).控制方式的異同
進程與線程的身份標示ID管理方式不一樣,進程的ID為pid_t類型,實際為一個int型的變量(也就是說是有限的):
/usr/include/unistd.h:260:typedef __pid_t pid_t;
/usr/include/bits/types.h:126:# define __STD_TYPE typedef
/usr/include/bits/types.h:142:__STD_TYPE __PID_T_TYPE __pid_t;
/usr/include/bits/typesizes.h:53:#define __PID_T_TYPE __S32_TYPE
/usr/include/bits/types.h:100:#define __S32_TYPE int
在全系統(tǒng)中,進程ID是唯一標識,對于進程的管理都是通過PID來實現(xiàn)的。每創(chuàng)建一個進程,內(nèi)核去中就會創(chuàng)建一個結構體來存儲該進程的全部信息:
注:下述代碼來自 Linux內(nèi)核3.18.1
include/linux/sched.h:1235:struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
...
pid_t pid;
pid_t tgid;
...
};
每一個存儲進程信息的節(jié)點也都保存著自己的PID。需要管理該進程時就通過這個ID來實現(xiàn)(比如發(fā)送信號)。當子進程結束要回收時(子進程調用exit()退出或代碼執(zhí)行完),需要通過wait()系統(tǒng)調用來進行,未回收的消亡進程會成為僵尸進程,其進程實體已經(jīng)不復存在,但會虛占PID資源,因此回收是有必要的。
線程的ID是一個long型變量:
/usr/include/bits/pthreadtypes.h:60:typedef unsigned long int pthread_t;
它的范圍大得多,管理方式也不一樣。線程ID一般在本進程空間內(nèi)作用就可以了,當然系統(tǒng)在管理線程時也需要記錄其信息。其方式是,在內(nèi)核創(chuàng)建一個內(nèi)核態(tài)線程與之對應,也就是說每一個用戶創(chuàng)建的線程都有一個內(nèi)核態(tài)線程對應。但這種對應關系不是一對一,而是多對一的關系,也就是一個內(nèi)核態(tài)線程可以對應著多個用戶級線程。還是請讀者參看《Linux線程的實質》普及相關概念。此處貼出blog地址:
http://my.oschina.net/cnyinlinux/blog/367910
對于線程而言,若要主動終止需要調用pthread_exit() ,主線程需要調用pthread_join()來回收(前提是該線程沒有被detached,相關概念請查閱線程的“分離屬性”)。像線發(fā)送線程信號也是通過線程ID實現(xiàn)的。
6).資源管理方式的異同
進程本身是資源分配的基本單位,因而它的資源都是獨立的,如果有多進程間的共享資源,就要用到進程間的通信方式了,比如共享內(nèi)存。共享數(shù)據(jù)就放在共享內(nèi)存去,大家都可以訪問,為保證數(shù)據(jù)寫入的安全,加上信號量一同使用。一般而言,共享內(nèi)存都是和信號量一起使用。消息隊列則不同,由于消息的收發(fā)是原子操作,因而自動實現(xiàn)了互斥,單獨使用就是安全的。
線程間要使用共享資源不需要用共享內(nèi)存,直接使用全局變量即可,或者malloc()動態(tài)申請內(nèi)存。顯得方便直接。而且互斥使用的是同一進程空間內(nèi)的互斥量,所以效率上也有優(yōu)勢。
實際中,為了使程序內(nèi)資源充分規(guī)整,也都采用共享內(nèi)存來存儲核心數(shù)據(jù)。不管進程還是線程,都采用這種方式。原因之一就是,共享內(nèi)存是脫離進程的資源,如果進程發(fā)生意外終止的話,共享內(nèi)存可以獨立存在不會被回收(是否回收由用戶編程實現(xiàn))。進程的空間在進程崩潰的那一刻也被系統(tǒng)回收了。雖然有coredump機制,但也只能是有限的彌補。共享內(nèi)存在進程down之后還完整保存,這樣可以拿來分析程序的故障原因。同時,運行的寶貴數(shù)據(jù)沒有丟失,程序重啟之后還能繼續(xù)處理之前未完成的任務,這也是采用共享內(nèi)存的又一大好處。
總結之,進程間的通信方式都是脫離于進程本身存在的,是全系統(tǒng)都可見的。這樣一來,進程的單點故障并不會損毀數(shù)據(jù),當然這不一定全是優(yōu)點。比如,進程崩潰前對信號量加鎖,崩潰后重啟,然后再次進入運行狀態(tài),此時直接進行加鎖,可能造成死鎖,程序再也無法繼續(xù)運轉。再比如,共享內(nèi)存是全系統(tǒng)可見的,如果你的進程資源被他人誤讀誤寫,后果肯定也是你不想要的。所以,各有利弊,關鍵在于程序設計時如何考量,技術上如何規(guī)避。這說起來又是編程技巧和經(jīng)驗的事情了。
7).個體間輩分關系的迥異
進程的備份關系森嚴,在父進程沒有結束前,所有的子進程都尊從父子關系,也就是說A創(chuàng)建了B,則A與B是父子關系,B又創(chuàng)建了C,則B與C也是父子關系,A與C構成爺孫關系,也就是說C是A的孫子進程。在系統(tǒng)上使用pstree命令打印進程樹,可以清晰看到備份關系。
多線程間的關系沒有那么嚴格,不管是父線程還是子線程創(chuàng)建了新的線程,都是共享父線程的資源,所以,都可以說是父線程的子線程,也就是只存在一個父線程,其余線程都是父線程的子線程。
8).進程池與線程池的技術實現(xiàn)差別
我們都知道,進程和線程的創(chuàng)建時需要時間的,并且系統(tǒng)所能承受的進程和線程數(shù)也是有上限的,這樣一來,如果業(yè)務在運行中需要動態(tài)創(chuàng)建子進程或線程時,系統(tǒng)無法承受不能立即創(chuàng)建的話,必然影響業(yè)務。綜上,聰明的程序員發(fā)明了一種新方法——池。
在程序啟動時,就預先創(chuàng)建一些子進程或線程,這樣在需要用時直接使喚。這就是老人口中的“多生孩子多種樹”。程序才開始運行,沒有那么多的服務請求,必然大量的進程或線程空閑,這時候一般讓他們“冬眠”,這樣不耗資源,要不然一大堆孩子的口食也是個負擔啊。對于進程和線程而言,方式是不一樣的。另外,當你有了任務,要分配給那些孩子的時候,手段也不一樣。下面就分別來解說。
進程池
首先創(chuàng)建了一批進程,就得管理,也就是你得分開保存進程ID,可以用數(shù)組,也可用鏈表。建議用數(shù)組,這樣可以實現(xiàn)常數(shù)內(nèi)找到某個線程,而且既然做了進程池,就預先估計好了生產(chǎn)多少進程合適,一般也不會再動態(tài)延展。就算要動態(tài)延展,也能預估范圍,提前做一個足夠大的數(shù)組。不為別的,就是為了快速響應。本來錯進程池的目的也是為了效率。
接下來就要讓閑置進程冬眠了,可以讓他們pause()掛起,也可用信號量掛起,還可以用IPC阻塞,方法很多,分析各自優(yōu)缺點根據(jù)實際情況采用就是了。
然后是分配任務了,當你有任務的時候就要讓他干活了。喚醒了進程,讓它從哪兒開始干呢?肯定得用到進程間通信了,比如信號喚醒它,然后讓它在預先指定的地方去讀取任務,可以用函數(shù)指針來實現(xiàn),要讓它干什么,就在約定的地方設置代碼段指針。這也只是告訴了它怎么干,還沒說干什么(數(shù)據(jù)條件),再通過共享內(nèi)存把要處理的數(shù)據(jù)設置好,這也子進程就知道怎么做了。干完之后再來一次進程間通信然后自己繼續(xù)冬眠,父進程就知道孩子干完了,收割成果。
最后結束時回收子進程,向各進程發(fā)送信號喚醒,改變激活狀態(tài)讓其主動結束,然后逐個wait()就可以了。
線程池
線程池的思想與上述類似,只是它更為輕量級,所以調度起來不用等待額外的資源。
要讓線程阻塞,用條件變量就是了,需要干活的時候父線程改變條件,子線程就被激活。
線程間通信方式就不用贅述了,不用繁瑣的通信就能達成,比起進程間效率要高一些。
線程干完之后自己再改變條件,這樣父線程也就知道該收割成果了。
整個程序結束時,逐個改變條件并改變激活狀態(tài)讓子線程結束,最后逐個回收即可。
到此,關于“Linux進程與線程的區(qū)別”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
網(wǎng)站題目:Linux進程與線程的區(qū)別
文章來源:http://chinadenli.net/article40/podeeo.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)、全網(wǎng)營銷推廣、標簽優(yōu)化、搜索引擎優(yōu)化、動態(tài)網(wǎng)站、App開發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)