1. 程式人生 > >進程線程及其狀態

進程線程及其狀態

文件描述符 原子操作 lee 具體實現 進程id join 進程調度 循環 寫時復制

進程線程及其狀態

進程

進程的概念

  • 進程就是執行中的程序。

進程的狀態

進程有五種狀態,分別是:

  • 新建:進程正在被創建
  • 運行:進程正在被執行
  • 阻塞:進程等待某個事件(如I/O操作)
  • 就緒:進程等待分配處理器
  • 終止:進程完成執行

進程調度流程圖

技術分享圖片

線程

線程的概念

  • 線程是程序執行流的最小單元,線程早期也有輕量級進程之稱。一個進程中可能包含多個線程。在系統內核層面,進程與線程並無本質的不同。進程與線程最大的不同點是資源分配。

線程與進程的比較

  • 線程與進程都可以實現多任務。
  • 線程是CPU調度的基本單元,進程是系統資源分配的基本單元。
  • windows下進程線程是涇渭分明,區別明顯的。在Linux中它們有很多共同特性。
  • 在早期Linux的內核結構中:進程和線程的區別只是創建子進程和子線程時,是否設置為共享內存,二者在內核中的存儲結構並無區別,系統調度的單位也是輕量級進程。2.6以後的Linux內核版本才將線程和進程完全獨立開來。
  • 線程的狀態改變只代表了CPU執行過程的改變,線程操作的資源仍然是進程的。除了 CPU外,計算機內的軟硬件資源的分配都是以進程為單位的。進程擁有一個完整的虛擬地址空間,不依賴於線程而獨立存在,而線程只是進程的一部分,與進程內的其他線程一起共享分配給該進程的所有資源。

線程的狀態

  • 同進程的實現原理類似,線程也可主要概括為五種狀態(實際上Linux將線程狀態細分為十幾種):
    • 新建,由於不需要進行必要的內存復制等工作,新建線程要比新建進程更快。
    • 就緒
    • 運行
    • 阻塞
    • 死亡,線程死亡後,也需要回收處理。
  • 調度的過程參考進程。

線程的內核調度

  • 多線程編程具有響應度高、資源共享、經濟和多處理器體系結構的利用四個優點。用戶線程是映射到內核線程池進行CPU調度的,映射關系模型包含有:

    • (1)多對一
    • (2)一對一
    • (3)多對多

內核調度圖

技術分享圖片

  • 這裏為什麽沒有一對多?因為線程是CPU資源調度的最小單位,即:單線程在一個時間點上只能利用到一個核心(進行一個原子操作),一個原子操作不能再分開由不同核心執行。而多核CPU在執行單線程任務時,可能會切換多個核心輪流來執行這個任務(每個原子操作的CPU核心可能並不相同),例如在執行循環時,這次循環和下次循環可能並不是同一個核心來執行的(這跟你的系統有關,但可以看到單線程最多只能占用到 (1/CPU核心數) 的CPU資源(超線程CPU占用1/(CPU線程數))。
  • 而資源上,多核CPU調用同一資源時,X86架構會使用總線鎖,對該資源進行鎖定,保證原子操作執行完整不被打斷。當操作完成時,會解鎖並通知其他線程,我操作完了,你們可以來操作了(實際上,此方法效率很低,僅作為最後一道保險)。
  • 因此確定一個操作是原子操作時,沒有必要浪費外圍昂貴的開銷再來給他加鎖,原子操作本身就是一道互斥鎖。互斥鎖的目的,也正是將一系列操作變為原子操作。

進程的誕生與消亡

  • 進程的誕生
    • (1)fork函數:子進程拷貝父進程的數據(具體實現是讀時共享,寫時復制)
    • (2)vfork函數:子進程與父進程共享數據
    • vfork是一個過時的函數,雖然與fork相比有那麽一點性能優勢,但其帶來一連串的坑並不那麽好填,不建議使用,除非你對性能追求到極致。
  • 進程的消亡
    • (1)正常結束和異常終止;
    • (2)linux系統設計時規定:每個進程結束時,系統會自動回收open但沒有close的文件資源,malloc但沒有free的資源等,但並不會回收進程本身占用的資源(即進程的屍體,主要包括進程本身的文件描述符對應的資源(task_struct結構體)和進程的棧空間),這需要由進程的父進程來完成回收(收屍)。
  • 僵屍進程
    • 在上述(2)中,如果父進程沒有結束,而且也不回收已結束的子進程(收屍),已經結束的子進程,就變成了僵屍進程。
    • 父進程可以使用wait或waitpid,顯式地回收子進程(剩余待回收)的內存資源並且獲取子進程退出狀態。
    • 父進程結束時也會自動回收僵屍進程,但應避免這種不嚴謹的方式。
  • 孤兒進程
    • 子進程還在執行,而父進程先結束了,子進程就成為了孤兒進程,托管到系統了。
    • 此時子進程的父進程變為了系統的init進程,init進程會在孤兒進程結束後自動回收孤兒進程的資源。

線程的誕生與消亡

  • 線程標識(線程ID)
    • 進程ID在整個系統中是唯一的。
    • 線程ID(pthread_t類型)只在它所屬的進程中有效。
    • pthread_t(Linux中為unsigned int類型,OS中為結構體指針,Windows中為handle句柄)用於聲明線程ID。
    • 函數:pthread_self取得自身線程ID。
  • 創建線程
    • 使用函數pthread_create,線程創建後,就開始運行相關的線程函數。
  • 退出線程。
    • 線程執行完畢。可以return,不能exit(exit是退出進程)。
    • 使用函數pthread_exit,主動退出線程。主線程使用該函數時,進程並不會結束,而是等待其他線程結束。
    • 進程結束時,線程也結束(線程依賴於其所在的進程)。
  • 線程回收
    • 由於線程使用的資源是屬於進程的,退出線程而進程仍然運行時資源並未完全釋放,形成僵屍線程。
    • pthread_join(tid)函數類似wait/waitpid函數,用於阻塞等待線程tid結束,調用它的線程一直等待到tid線程結束時,tid線程資源就被回收。
    • pthread_detach(tid)函數線程分離,讓系統自動回收tid線程。
    • 按以下步驟回收:
      • pthread_attr_t attr;//線程創建前,定義線程屬性
      • pthread_attr_init(&attr);//進行初始化線程屬性
      • pthread_attr_getdetachsate(&attr,&status);//獲取分離狀態
      • pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//設置線程分離狀態.
      • pthread_create(&tid, &attr,func,NULL);//創建線程
      • pthread_attr_destroy(&attr);//線程結束時,調用回收函數
    • 線程回收代碼示例:
    void * func(void *p)
    {
        printf("我是子線程\n");
    }
    int main(int argc, char *argv[])
    {
        pthread_attr_t attr; //定義一個變量
        pthread_t tid;
        pthread_attr_init(&attr);//初始化
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//設置分離
        pthread_create(&tid, &attr, func, NULL);//創建線程
        sleep(1);//等1秒讓子線程執行完
        pthread_attr_destroy(&attr);//釋放
        return 0;
    }

進程線程及其狀態