1. 程式人生 > >什麼是程式,程序和執行緒?三者之間有何關係?

什麼是程式,程序和執行緒?三者之間有何關係?

程序

程序與執行緒的歷史

我們都知道計算機是由硬體和軟體組成的。硬體中的CPU是計算機的核心,它承擔計算機的所有任務。 作業系統是執行在硬體之上的軟體,是計算機的管理者,它負責資源的管理和分配、任務的排程。 程式是執行在系統上的具有某種功能的軟體,比如說瀏覽器,音樂播放器等。 每次執行程式的時候,都會完成一定的功能,比如說瀏覽器幫我們開啟網頁,為了保證其獨立性,就需要一個專門的管理和控制執行程式的資料結構——程序控制塊。 程序就是一個程式在一個數據集上的一次動態執行過程。 程序一般由程式、資料集、程序控制塊三部分組成。我們編寫的程式用來描述程序要完成哪些功能以及如何完成;資料集則是程式在執行過程中所需要使用的資源;程序控制塊用來記錄程序的外部特徵,描述程序的執行變化過程,系統可以利用它來控制和管理程序,它是系統感知程序存在的唯一標誌。

在早期的作業系統裡,計算機只有一個核心,程序執行程式的最小單位,任務排程採用時間片輪轉的搶佔式方式進行程序排程。每個程序都有各自的一塊獨立的記憶體,保證程序彼此間的記憶體地址空間的隔離。 隨著計算機技術的發展,程序出現了很多弊端,一是程序的建立、撤銷和切換的開銷比較大,二是由於對稱多處理機(對稱多處理機(SymmetricalMulti-Processing)又叫SMP,是指在一個計算機上彙集了一組處理器(多CPU),各CPU之間共享記憶體子系統以及匯流排結構)的出現,可以滿足多個執行單位,而多程序並行開銷過大。 這個時候就引入了執行緒的概念。 執行緒也叫輕量級程序,它是一個基本的CPU執行單元,也是程式執行過程中的最小單元,由執行緒ID、程式計數器、暫存器集合 和堆疊共同組成。執行緒的引入減小了程式併發執行時的開銷,提高了作業系統的併發效能。 執行緒沒有自己的系統資源,只擁有在執行時必不可少的資源。但執行緒可以與同屬與同一程序的其他執行緒共享程序所擁有的其他資源。

 程序與執行緒之間的關係

執行緒是屬於程序的,執行緒執行在程序空間內,同一程序所產生的執行緒共享同一記憶體空間,當程序退出時該程序所產生的執行緒都會被強制退出並清除。執行緒可與屬於同一程序的其它執行緒共享程序所擁有的全部資源,但是其本身基本上不擁有系統資源,只擁有一點在執行中必不可少的資訊(如程式計數器、一組暫存器和棧)。

重點:

  • 程序:

      優點: 同時利用多核cpu,能夠同時進行多個操作

      缺點:耗費資源(每個程序核心都要為其開闢線性地址空間,概念清看下面的)

  • 執行緒:

      優點:共享記憶體,I/O操作可以併發,比如爬蟲

      缺點:搶佔資源,核心會枷鎖,容易造成死鎖

    1. 子程序不是越多越好,跟cpu核數相近或處理速度夠快,根據處理速度進行啟動,太多的話,除了資源浪費,還有程序上下文切換,cpu的中斷interrupt,如果涉及到io操作的話還有模式轉換
    2. 子執行緒也不是越多越好,程序上下文切換,cpu的中斷interrupt
    3. 計算機最小的任務執行單元:執行緒。說的微執行緒:協程 後面有介紹,其實就是控制執行緒的執行位置
    4. I/O操作不佔用cpu時間,而是發生使用者核心兩種模式轉換
    5. I/O密集型應用(cpu) ======= 多執行緒
    6. 計算密集型應用(用cpu) =========多程序
    7. python的程序上有個GIL 全域性解釋性鎖,這個會造成,一個程序的多個執行緒,不能同時使用多個cpu,而是cpu每次只能選一個執行緒執行,因此,多執行緒在cpu執行的是無效的。但是在I/O操作的時候是可以同步的,比如time.sleep就是io 操作,多執行緒,可以同時等待
主執行緒
比如我們寫的py檔案,執行的時候,所有程式碼是如何向下執行呢?肯定有個主執行緒的。
我們建立多執行緒時候,這些執行緒都是子執行緒,那肯定有個主執行緒的。

程序 和 執行緒的概念

程序和程式關係

程序
程式例項 程式子集 有所謂生命週期,可以kill叼 比如你安裝的word 是一個程式 ,你開啟一個文件是一個程序,可以關掉。
程序要想完成併發執行的功能就要程序切換
程序切換 ,上下文切換,程序執行,說明在cpu的暫存器裡面有資料了。假如5條資料現在有兩條,就切換了,現在要儲存現場,回來時候 要恢復現場。如果機器上有幾千個程序,會切換 上萬個切換需要時間,程序切換時監控程式來完成的,也就是核心,消耗時間正常程式執行空間是使用者空間,佔用在核心,說明大量時間消耗到程序切換。不好。
首先我們知道cpu是執行二進位制的指令的,而對應的就有兩種程式設計:解釋形語言 和 編譯形語言 兩種程式設計, c++就是編譯成二進位制一起執行的,所以c++是編譯形 解釋型:JavaScript等。所以cpu是執行指令流的。
程序

由父程序 fork 自身而來
ls 這個程序的父程序是 shell frok而來父程序 啟動子程序,子程序記憶體空間是在父程序空間,一旦外部載入資料會重新開闢一個記憶體空間程式時由指令和資料組成。程式時位於硬碟上的,是死的,只有當核心建立資料結構,分配了資料資源,cpu資源,處於活動狀態,才有真正的執行價值,才會被拿來一個個被執行。程式內的指令能不能並行執行?即程序內的多條指令流。單核是不能了。雙核的話,要是第一條執行到一半依靠第二個指令的結果,所以不能同時執行的。這是一個執行流的情況下。

cpu在執行程式時,什麼叫程序?


  • 出現核心之前

程序在發起之前,是一個程式,在磁碟上。比如我們ls 多個使用者可以發起多個ls程序。不互相干擾,不會意識到彼此存在。程序是程式的副本。
程式本來在硬體執行。但是指令+資料在記憶體中。 所以cpu會執行一條拿一條,為什麼要用核心呢?我們程式在cpu執行,再完之前,不會退出的, 我想讓另一個程式在cpu執行,是不行的。
比如第一個在執行時,可能會產生I/O,過程中比如需要開啟一個檔案,這個檔案在磁碟上很大,而程式必須將這個檔案內容載入進記憶體的這個程式的資料區域之中。硬碟速度很慢。我們cpu這個時間很快,就載入一下就休息一下。cpu就閒置了。
我們為了解決多個問題,就在cpu執行多工了,同時執行。記憶體中有多個程式了。那第一個載入資料了,第二個怎麼執行呢?搶過cpu?就打架了,而記憶體也可能打架,這樣就需要一個監控工具在上層。所以核心這個資源排程監控工具。
linux 本身就是搶佔式多工。

  • 出現核心之後
    有核心這個監控排程工具:核心就負責這樣幾項: 以後這個程式再想執行,是直接向核心註冊申請的,核心決定,你可以使用cpu,使用2ms,分配給你,不管2ms以後你有沒有結束,都要交給另一個程式。這樣輪詢交替,讓使用者看起來是同時執行的。其實在同一時間只能有一個程式來佔用一個cpu執行的。
  • 所以,程序的啟動排程都是由核心完成的。
    在沒有 核心之前,程式執行在記憶體。是作業。
  • 後來為甚麼叫程序呢?
    核心必須把有限資源分配個各個貪婪的程式,會把cpu切割成片,時間片,我們cpu是按照時間流逝進行完成的。記憶體是拿空間用來儲存。cpu是時間
    核心也是程式,所以有核心空間,使用者程式,在使用者空間,使用者程式的單獨空間是可以伸縮的。都直接把程式的空間連續起來是有弊端的,伸縮,會導致很多記憶體碎片。以後再想找連續空間就沒了
  • 記憶體單位:頁面
    我們大多數記憶體空間叫分頁記憶體空間,跟我們磁碟一樣,也是分塊的。叫記憶體頁面 而能存資料的叫記憶體頁框 page frame

  • MMU:記憶體控制單元,其實是星型方式才具備的元件,尤其是x86架構。在arm上有沒有不知道。主要是以x86架構的cpu說明的
    mmu:memerry manage unit

    mmu 出現的主要目的是為了實現記憶體分頁

    我們應該知道,一個程式被載入到記憶體,並不是所有指令在執行,比如ls /etc 跟ls -l /etc 這個ls 的執行程式碼都不一樣。所以我們的ls 程式不是所有指令都載入進去的。需要-l再把-l的指令載入進來。 資料量不同。存的頁框不同。需要1個我們分1個,需要2個分2個頁框,不夠了,找其他頁框,即便不連續也可以,最好連續。 只要我們核心追蹤著這個程序知道這個記憶體頁框就行。
    我們核心,都給程序虛擬出了一段假的記憶體空間,讓程序看起來是連續的。
    一個程序最多能識別多大記憶體呢?
    32位 4G
    64位 4G^4G

        一個程式的4G記憶體空間,它的所有執行操作都是通過呼叫核心中的系統呼叫來完成的。
        後來程式設計的時候不是在cpu上使用指令集程式設計的。而是使用系統呼叫。
        所以程序是知道核心存在的,所以程序要跟核心和平共處。
    

    線性地址空間:
    因此32位的這4g虛擬 空間,有1g分給核心,剩下的程式自己用。而實際上我們的實體記憶體只有512M。但是為了統一管理,為了使得程序能夠以統一風格,任程式設計師程式設計,不管你是512M實體記憶體還是1g實體記憶體,都是虛擬出4g記憶體,1g分給核心,其他分給程式的。這段空間是假的,是核心虛擬給程序的。這段地址空間被稱為線性地址空間,看上去連續的,其實不連續。
    我們的3g開始是程式的執行入口的,開始一段是記錄檔案的格式等內容,前面是為空的
    真實記憶體比如512M,但是會虛擬出4G線性空間給每個程序,每個程序都會以為4G虛擬空間,但是 不會用4G,該用多少用多少。

    怎麼使用這4G空間呢?\

    剛一直在講,程式是由指令加資料組成的,這個空間內放著程式的指令和資料,除了核心空間外。光有指令和資料 還不夠,實際上在實體記憶體中是可能不連續的頁框,線性地址空間的對應指令指向實體記憶體的不連續的頁框。這樣單個程序看到的虛擬空間只是 自己單獨存在的。各自不互相影響。

    swap 記憶體的缺頁異常 大的異常

    這樣總有一天記憶體耗盡了。就會將記憶體頁面頁框內的 資料轉移到我們硬碟上,叫做swap交換分割槽。用於臨時存放記憶體中暫時用不上的記憶體頁面。用的什麼演算法呢?lru演算法,lru:最近最少使用的記憶體頁面資料。但是如果人家程序醒了,訪問被拿走的資料,核心會阻止它,告訴它沒有,把它的拿回來從swap內,再把別人的騰空放到swap內。這就是所謂的缺頁異常。 我找的頁面不在了。我的內容本來在記憶體中,但是記憶體中不在了。這種情況就會產生I/O。為什麼產生I/O呢?因為核心要從磁碟上搶回資料,從swap內。與磁碟操作就產生I/O,這就是大的異常。
    

    小的異常 記憶體對映,mmap

    我們知道,程式在執行時,都是動態連結的方式連結到好多個庫上的。如果程式執行要依賴這個庫,就會把這個庫載入到記憶體上來的,因為程式的某些功能要依賴這個庫的執行來實現的。但是這個庫叫so ,共享物件,多個程序可以共同使用的。比如程序1 需要 lib1載入到記憶體的一個頁面上。程序2啟動也需要依賴lib1,庫檔案都是執行的程式碼,不會改變。
    程序2 通知核心載入lib1,由於lib1已經由程序1載入到記憶體在,不會再次載入,但是程序2沒有虛擬記憶體對應的頁面,就會出現缺頁異常,這時核心就會說,等一下,記憶體中有已經載入了lib2,安撫一下,(當然不是其獨享的。如果其他程序需要依賴某個庫,就會對映到哪個程序的空間)   將載入的頁面告訴程序2.
    而lib2載入到記憶體的空間由兩個程序共享,都以為是自己的。但是不會 隨著一個 結束而釋放,只有全部釋放才 釋放。而這段共享空間是,記憶體對映,mmap==memery map。 這段記憶體是共享記憶體空間,是對映到程序裡面去的。
    

    記憶體洩露:

    對於linux使用者空間中,init是所有程序的老祖宗。除了init之外,其他程序如果需要其他任務,就會建立子程序。子程序都是由其父程序通過向核心fork系統呼叫複製的,父程序通過自身fork一個一模一樣的子程序除了。由子程序完成特定任務,一旦子程序完成任務,就清理子程序。kill子程序,終止子程序
    一旦父程序都over了,如果還有子程序。所有子程序都成孤兒了。所有程序的父程序是init。父程序在over之前,要給子程序找一個新爹,即監護程序。如果沒有給子程序找新爹,這個子程序就成孤兒了,所有的孤兒程序就會被回收了,無法被回收佔用的記憶體就無法被回收了,無法被分配了。這樣分配出去的記憶體無法回收舊造成記憶體洩露了。這就找程式設計師了。程式問題。重啟可以,記憶體重新分配。  因為我們無法追蹤孤兒程序,無法kill。
    linux使用者空間中,init是所有程序的老祖宗。除了init之外,其他程序如果需要其他任務,就會建立子程序。子程序都是由其父程序通過向核心fork系統呼叫複製的,父程序通過自身fork一個一模一樣的子程序除了
    

    task_struc

    就算是父程序kill殺掉子程序,也會像核心去申請終止 才能終止。因此,在核心中為程序建立了資訊庫,戶口簿,
    記錄了每一個程序的  程序號,程序名  程序父程序  程序使用的cpu時間累積 分配的記憶體也,父程序編號,佔用了哪些cpu 等等。監視無處不在。這些對於linux核心叫 task_struct,任務結構。每一個任務都有一個結構,所以每建立一個子程序,都會向核心註冊一個任務,核心建立一個任務結構,來儲存任務的各相關屬性資訊,核心從而追蹤他所使用的各相關資源了。

    雙向迴圈的連結串列

    核心這種任務有n個,那核心是如何找到每一個呢?
            這些資料結構,是c實現的。python簡單的多。單c的效率高。
            著每個結構對程式語言來講是資料結構。描述 資料的屬性資訊。 程序號 id  父程序等屬性資訊。這種描述程序格式叫資料 結構。這種資料結構用c描述要複雜的多。
            每個程序有一個任務結構,那我們核心怎麼知道有多少個呢,如果方便的找到任意一個ne ?
            而我們的linux 的核心也是程式,而程式也要 儲存到記憶體當中。核心也要在cpu執行之後,才能存到記憶體中的。核心要自身追蹤這些結構,是通個c語言的雙向迴圈的連結串列 實現的。每個結構也有結構的編號。
            第一個結構尾部記錄第二個結構的記憶體地址編號。而第二個結構首部 存有第一個結構的地址編號。
    
    
    沒有程序檢視工具之前,我們要檢視系統有多少個程序,就遍歷上圖的列表計算一次。

    程序屬性在linux系統的存放位置:

     核心不能隨便訪問。所以我們需要偽檔案系統。所以每個程序的程序號在proc下。這些個程序號下存著各種屬性資料。啟動命令,那個cpu執行過,哪些記憶體。
    
    程序管理工具實現:
            我們所謂的程序檢視工具,就是獲取為檔案系統的程序屬性的。
    
    
    記憶體中,有1g是我們核心空間的,使用者空間存放我們要執行程式的指令,cpu執行指令的方式有三種,順序、迴圈、選擇這三種執行。
    
            比如一個程序申請的4g空間,核心佔1g,而程式指令在使用者空間,cpu依次讀取進行,當執行到一個點無法完成,就會生成一個子程序,由於同一時間1個cpu只能做1件事情,cpu是時間片切片機制,這時父程序就會休眠。而子程序又會申請開闢一個4g的線性地址 空間,cpu又會繼續執行子程序的指令,執行完後,結構返回給父程序。
            即使有兩個cpu 程式也不能即使執行。因為父程序等著子程序返回結構才能繼續工作。

    執行緒: thread

    程式時由指令和資料組成。程式時位於硬碟上的,是死的,只有當核心建立資料結構     ,分配了資料資源,cpu資源,處於活動狀態,才有真正的執行價值,才會被拿來一個個被執行。
        程式內的指令能不能並行執行?即程序內的多條指令流。單核是不能了。雙核的話,要是第一條執行到一半依靠第二個指令的結果,所以不能同時執行的。這是在一個執行流的情況下。
            那我們兩個cpu幹什麼呢?可以執行多個程序啊,雖然不能執行一個程序的多個指令
    分成n個執行流,每個流內都有指令和資料,是不互相干擾的。
    
    這樣就需要一個程序空間內部,並行執行。一個程序內部有多個執行流來並行操作,但是出現資源爭搶線性
            執行緒是一個程序內部的多個執行流
                多個執行緒不能同時開啟一個資原始檔,而lib儘管可以共享,也是在核心級別存在的。
                資源傳送爭搶的區域叫臨界區。
            單程序、單執行緒:一個程序記憶體空間內有一個執行流。
            單程序、多執行緒:一個程序內的指令集分成多個執行流
    
            不管你的一個程序空間內部有多少個執行流,說白了就是多少個執行緒,但是你有一個cpu,多個執行緒沒有什麼明顯的地方,除了麻煩以外。
            多個cpu就有好多好處了。
    
    web伺服器工作模型及短板。:
            web伺服器:web服務程序。有一個使用者訪問時,我不能直接訪問,否則有問題,那第二個來訪問怎麼辦,只能等著。    
            怎麼辦呢?給每個使用者生成一個子程序,每個使用者一個子程序,一個使用者需要10M記憶體,1000個使用者就是10g。還要涉及到程序切換。但是如果訪問主頁,主頁這個記憶體空間的話,每個使用者這一個子程序就要開闢一塊,那我們的伺服器資源都要撐壞了。所以為了讓眾多使用者共享一個資源來說,我們就要採用執行緒模型。web服務比如啟動一個程序,裡面分配多個執行緒,這樣我們就開啟一份資料就行了。cpu多的話就好了。
            但是多個I/O爭用的。雖然我們cpu多個,裡面處理資料很快,但是我們網絡卡就有一個,還是會堵在網絡卡佇列這裡。
    所以我們要理解原理,我們就知道系統瓶頸短板在哪了。
    
    
    對於linux 而言,不是執行緒作業系統。windows solorias 都是微核心的執行緒作業系統。linux是單核心的。
    
    執行緒跟核心本身的關係並不大,是由執行緒庫實現的。迄今為止,linux核心中維持的每一個執行緒,跟程序毫無區別。

    LWP

    就算你生成了n個執行緒,但是在 linux看來跟程序是一樣的。只不過是佔用記憶體大小變小,稱為輕量級程序。LIGHT WEIGHT PROCESS
    linux 原生核心不支援執行緒,好多愛好開發者提供了好多實現方式,linux核心實現一套   紅帽  一套   其他一套  三種實現需要執行緒庫的支援。
    
     執行緒死鎖
    執行緒不一定是好事,多個執行緒不能同時搶佔一個檔案。核心會給資源枷鎖。
    執行緒資源浪費:
        死鎖:12的資源 21的資源,都在等著釋放。
               1 等著2拿的資源,cpu過一下看一回,一直cpu看,浪費時間。
        liunx內部有自選鎖。很複雜

    模式轉換:核心模式 使用者模式

    為了避免使用者的程序作業系統資源,cpu是有保護進位制的。cpu把它能操作執行的指令時分4類。分別放在環0 1 2 3 上。
    使用者程序只要不是以核心方式開發的,是以自己程式設計方式開發的基本程式,是不能呼叫特權指令,一旦嘗試呼叫cpu的特權指令,cpu就會喚醒核心的。而普通程式要想訪問硬體,就要訪問特定的程式介面訪問特權指令,要通過系統呼叫通知核心。
            庫呼叫,系統呼叫。
            庫呼叫是執行庫的程式碼的,而系統呼叫是執行核心程式碼的。
            程式執行,到核心執行 在回到使用者空間這就叫做模式轉換。
    
    
            所以產生系統呼叫在核心浪費的時間越多,cpu越浪費。所以大部分時間70%應該浪費到使用者空間,才會產生生產力
    比如我們的ftp伺服器,只有在訪問硬碟資料資源,網絡卡封包解包等才會執行核心模式操作。所以浪費在核心模式的程式大部分是系統程式。而使用者工作程式,應該大部分時間需要浪費在使用者空間。

    程序切換: 上下文切換 程序的環境切換

    cpu只有一顆,在同一時刻,cpu執行的程序只有一個,不能讓一個程序一直佔用cpu,我們一定要讓其及時切換交給其他程序。
             暫存器:儲存當前程序的執行狀態碼
             1級快取  2級快取 3級快取 記憶體  硬碟
    
            而程序切換,我們要把暫存器當前的執行狀態碼,儲存下來。儲存現場。
            每個程序,核心都會在記憶體維護一個task_starck 任務結構,用於儲存程序的屬性狀態資訊。一旦切換程序,就會儲存當前狀態。再把另一個程序,本來儲存在記憶體的任務結構的狀態資訊,從新交給cpu執行。在這個切換時間內,cpu並沒有幹活。所以這個切換時間越快越好。
            那是越頻繁越好呢??  大量時間cpu都浪費到來回切換了。

    時鐘中斷

    當然核心也有核心工作頻率的,靠時鐘驅動的。比如核心100hz/s   1s 震盪100次。核心工作hz 200 500 1000  s是越快越好嗎?
            每次HZ翻轉時,時鐘震盪翻轉時,都會產生時鐘中斷髮生1s,linux是搶佔式多工,如果中斷就會發生搶佔在時鐘中斷週期走完時。
    
            cpu時間走完了,或者核心分配的時間走完了。
    
            為什麼會搶?
                程序是有優先順序的。
                程序排程,是核心的核心功能之一。要公平合理。對於那些需要佔用特權資源的。緊急的程序優先順序高。
    
                公平:
                        結果公平
                        七點公平
    
                程序的排程演算法非常重要:
                        統籌方法
    
                        程式=演算法指令+資料 結構
    
    
                    優先順序高的排前面,低的排後面。排1對合適嗎?
                    我們按照優先順序排隊,優先順序相同的排一隊。在演算法過濾
    
    
                    Big O :演算法優先的判斷工具
                    O(1)
                        橫 佇列長度   縱是掃描時間

    核心的功用:程序管理、檔案系統、網路功能、記憶體管理、驅動程式、 安全功能等

    Process: 執行中的程式的一個 副本,是被 載入記憶體的一個指令集合

    程序ID (Process ID ,PID )號碼被用來標記各個程序
    UID 、GID 、和SELinux 語境決定對檔案系統的存取和 訪問許可權,通常 從執行程序的使用者來繼承存在生命週期

    task struct :Linux 核心儲存程序資訊的資料結構格式

    task list :多個任務的的task struct 組成的連結串列

    程序建立:

    init :第一個程序
    父子關係
    程序:都由其父程序建立,CoWfork(), clone()

    程序優先順序:

    • 系統優先順序: 數字越小,優先順序越高
    • 0-139 (CentOS4,5)各有140 個執行佇列和過期佇列
    • 0-98 ,99 (CentOS6) )
      -實時優先順序(rtpri): 99-0 :值最大優先順序最高
      -nice 值:-20 到19 ,對應 系統優先順序100-139 或99
    • cpu分配演算法Big O :時間複雜 度 ,用時和規模 的 關係
      O(1), O(logn), O(n) 線性, O(n^2) 拋物線, O(2^n)
      -LRU :Least Recently Used 近期最少使用演算法