進程和線程
進程
進程是計算機中已運行程序的實體,一個正在運行的程序就是一個進程。
進程模型
每個進程擁有自己的程序計數器、寄存器和變量等,操作系統會用一張表記錄下每個進程中的信息。
就單核 CPU 而言:在某個時間點中,并不是所有進程都占用 CPU 資源,而是在 CPU 的高速切換中,每個進程都被 CPU 所運行。
如果某個時間點只有一個進程運行,其他的進程的變量等信息就會被操作系統使用一張表來記錄,以便下一次 CPU 運算時調用,保持每次的結果相等,這張表稱為進程表。
也就是說,每個進程在退出 CPU 調用的時候會把這進程表更新,以備下一次進入 CPU 時,數據的同步。
進程的狀態
進程有三種狀態:
運行態(這個時刻進程實際占用 CPU 資源)
就緒態(可以運行,但是因為其他的進程正在運行而暫時停止)
阻塞態(除非某種外部事件發生,否則進程不能運行)
這三種狀態可以互相切換:
- 運行態 >> 阻塞態
操作系統發現進程無法運行下去的時候就會發生這個轉換。比如:當一個進程從 I/O 設備讀寫文數據的時候,如果一直沒有完成,那么就會進入阻塞狀態。
- 運行態 >> 就緒態
這個轉換是由于進程調度程序引起的,進程調度程序是操作系統的一部分,但是進程感覺不到進程調度程序的存在。因為系統認為一個進程占用 CPU 時間過長,那么就會讓其進程退出,使其他的進程使用 CPU 的時間。也就是從運行態轉換到了就緒態。
- 就緒態 >> 運行態
這個轉換是上一個轉換的逆向,原理相同,都是因為公平的原則,讓所有的進程公平起見,占用太久的進程需要使用 CPU 時間,而讓就緒態進程轉換到運行態。
- 阻塞態 >> 就緒態
比如:當一個進程從 I/O 設備讀寫數據完畢之前是處于阻塞狀態,而讀寫完畢之后就轉變為了就緒態。如果這個時候 CPU 沒有其他需要運行的進程,那么就會發生從就緒態轉變為運行態。
線程
一個進程最少有一個線程,也可以有多個線程。線程有的時候與進程類似。假如進程是管家,那么線程就是苦力,要賣力的為進程干活兒。
線程像一種輕量級的進程,它比進程更容易創建,也更容易撤銷。有時候,創建一個進程比創建一個線程多一到兩個數量級。所以,這就體現出了線程的必要性。
假設你在一個文字處理軟件中編寫文字,當你覺得寫的文字足夠多以至于需要保存了,如果這個文字處理軟件是一個單線程進程,那么在你保存的同時,你就不能做其他的事情,比如繼續編寫文字。或者發生了另外一種情況:這個文字處理軟件開發者覺得這個軟件不夠好,對其做了改進,現在這個軟件可以自動保存了。
這樣就不需要擔心編輯很多內容突然斷電導致災難性的后果。
第二天你還是生氣的對軟件開發者說道,在我輸入十幾個字之后電腦就動不了了!
在單線程的進程中,程序只能做一件事,也就是說,按照上面的例子,文字編輯器添加了自動保存的功能,在保存的同時,用戶不能夠對其輸入,只能夠等待保存完成之后繼續輸入。
再舉一個例子:
老劉正在坐著清閑的看著報紙,突然接到電話,說他的孫女發燒了需要照顧,正在他在趕路去孫女家的時候,朋友老李打電話讓他出去喝茶但是他不得不照顧完孫女再去喝茶。
老劉就類似于一個線程,而他接到電話要去照顧孫女的時候,他就不能答應老李的邀請。
要是老劉能分身的話,該多好!
事實上,老劉不能分身,而計算機中,可以使用多線程來完成任務。
多線程
上面所說,一個進程至少有一個線程,也可以有多個線程。為什么我們不使用多個進程合作而使用多個線程合作呢?
試想:前面的文字處理軟件,我們可以再開啟一個進程來為軟件保存內容,可是,這樣能做到嗎?
一個進程可以認為自己獨占了內存,是看不見其他進程的內存。那么上面的想法,還可行嗎?
使用線程就能完美的解決這個問題了,因為多個線程是可以共享一個進程里的資源。
這樣那個文字處理軟件開發者可以使用多個線程共同合作來完成任務:一個線程用來更新顯示,一個線程用來保存,一個線程用來監聽鍵盤鼠標......
我們可以稱用來保存的那個線程為守護線程。
經典的線程模型
線程的實現有 3 種模型:
- 多對一(M:1)的用戶級線程模型
- 一對一(1:1)的內核級線程模型
- 多對多(M:N)的兩級線程模型
用戶級線程模型:
在這個模型中,線程的創建、調度、同步等所有的細節全部由進程的用戶空間線程包來處理,這樣就不需要內核來接管這些操作,這種模型可以在不支持線程的操作系統上實現。
在用戶空間管理線程的時候,每個進程就需要一張線程表,用來記錄與跟蹤進程中的線程,這張表與進程表類似, 但是線程表僅僅記錄各個線程的屬性。比如:每個線程的程序計數器、堆棧指針、寄存器、狀態等等。
用戶級線程有一個優點,就是允許每個進程有自己定制的調度算法。例如,在某個應用程序中有些垃圾收集線程的應用就不需要擔心在不適合的時候停止導致食物。
用戶級線程有一個問題:如果一個線程開始執行,那么該進程中的其他線程就不能執行,當這個運行的線程被阻塞了,那么操作系統將認為整個進程都是阻塞住了,進而該進程中的其他線程無法調度。
線程與進程類似,但是也有不同之處:
a. 線程沒有時鐘中斷
b. 線程可以調用 yield 函數,在執行這個函數之后就可以調用線程調度程序來選擇另外一個需要運行的線程。而進程不可以。不同的進程所擁有的資源都是相對獨立的,所以不同進程之間是競爭關系,而線程之間是相互合作用來完成某個任務而存在,是合作關系。所以進程沒有那么高尚,將自己寶貴的時間片讓給其他進程。
內核級線程:
在這個模型中,進程中就不需要線程表了,也沒有線程表。但是,內核中有用來記錄系統中所有線程的線程表。內核中的線程表與上述用戶級線程表記錄的信息是一樣的。
當一個進程的線程阻塞住了,內核可以選擇是讓其阻塞住線程的進程中其他線程執行或者是讓其他進程中的線程執行。
在內核中創建與銷毀線程的開銷相對來說是比較大的。
混合實現:
用戶級線程與內核級線程都有各自的優點與缺點,人們研究了許多鍵這兩種優點結合起來的方法。
有一種方法就是,使用內核級線程,然后將用戶級線程與謳歌或者全部內核級線程多路復用起來,這樣,內核只識別內核級線程,并對其進行調度,其中一些內核級線程會被多個用戶級線程多路復用。這樣每個內河集線程有一個輪流復用的用戶級線程集合。