面試筆記:操作系統相關——進程、線程

一、線程與進程的區別

  • 進程是系統分配資源的最小單位
  • 線程是CPU調度的最小單位
  • 由於默認進程內只有一個線程,所以多核CPU處理多進程就像是一個進程一個核心

進程

進程是系統資源分配的最小單位, 系統由一個個進程(程序)組成。
一般情況下,包括文本區域(text region)數據區域(data region)堆棧(stack region)

文本區域存儲處理器執行的代碼
數據區域存儲變量和進程執行期間使用的動態分配的內存;
堆棧區域存儲着活動過程調用的指令和本地變量。

因此進程的創建和銷燬都是相對於系統資源,所以是一種比較昂貴的操作。
進程有三個狀態:

等待態: 等待某個事件的完成;
就緒態: 等待系統分配處理器以便運行;
運行態: 佔有處理器正在運行。

進程是搶佔式的爭奪CPU運行自身,而CPU單核的情況下同一時間只能執行一個進程的代碼,但是多進程的實現則是通過CPU飛快的切換不同進程,因此使得看上去就像是多個進程在同時進行.

通信問題: 由於進程間是隔離的,各自擁有自己的內存內存資源, 因此相對於線程比較安全, 所以不同進程之間的數據只能通過 IPC(Inter-Process Communication) 進行通信共享.

線程

  • 線程屬於進程
  • 線程共享進程的內存地址空間
  • 線程幾乎不佔有系統資源

通信問題: 進程相當於一個容器,而線程而是運行在容器裏面的,因此對於容器內的東西,線程是共同享有的,因此線程間的通信可以直接通過全局變量進行通信,但是由此帶來的例如多個線程讀寫同一個地址變量的時候則將帶來不可預期的後果,因此這時候引入了各種鎖的作用,例如互斥鎖等。

同時多線程是不安全的,當一個線程崩潰了,會導致整個進程也崩潰了,即其他線程也掛了,
但多進程而不會,一個進程掛了,另一個進程依然照樣運行。

  • 進程是系統分配資源的最小單位
  • 線程是CPU調度的最小單位
  • 由於默認進程內只有一個線程,所以多核CPU處理多進程就像是一個進程一個核心

線程和進程的上下文切換

進程切換分3步:

  1. 切換頁目錄以使用新的地址空間
  2. 切換內核棧
  3. 切換硬件上下文

而線程切換隻需要第2、3步,因此進程的切換代價比較大
協程

協程是屬於線程的。 協程程序是在線程裏面跑的,因此協程又稱微線程和纖程等
協沒有線程的上下文切換消耗。協程的調度切換是用戶(程序員)手動切換的,因此更加靈活,因此又叫用戶空間線程.
原子操作性。由於協程是用戶調度的,所以不會出現執行一半的代碼片段被強制中斷了,因此無需原子操作鎖。

協程的實現:迭代器和生成器

迭代器: 實現了迭代接口的類,接口函數例如:current,key,next,rewind,valid。迭代器最基本的規定了對象可以通過next返回下一個值,而不是像數組,列表一樣一次性返回。語言實現:在Java的foreach遍歷迭代器對(數組),Python的for遍歷迭代器對象(tuple,list,dist)。
生成器: 使用 yield 關鍵字的函數,可以多次返回值,生成器實際上也算是實現了迭代器接口(協議)。即生成器也可通過next返回下一個值。

協程舉例:在Python中,使用了yield的函數爲生成器函數,即可以多次返回值。則生成器可以暫停一下,轉而執行其他代碼,再回來繼續執行函數往下的代碼。

作者:feng409
鏈接:https://juejin.im/post/5b0014b7518825426e023666
來源:掘金


由該問題引出有一個有趣的東西。曾經被同學問過:在一個大型多人連線遊戲平臺中,每個玩家有一個人物與一定信息需要在大廳中顯示和交互,此時如果讓你設計這個平臺,多個玩家控制多個人物時是使用多線程還是多進程來跑?

答案顯而易見的是:進程

原因很簡單,不同進程間的資源是相互獨立互不影響的。如果使用多線程,當大廳中某個玩家出現出現問題,那麼該大廳中控制所有玩家的進程內所有線程共用的資源將被影響,導致一個玩家出問題,整個平臺崩潰。 而用多進程來跑時,一個玩家出現問題,退出該進程即可,該玩家可以重新登陸,而不影響平臺中其他玩家的使用。

這個問題還有一個例子就是瀏覽器,市面上大多數瀏覽器如IE、Firefox等都是使用多線程來顯示多個網頁,一個瀏覽器即一個進程,一個瀏覽器多個網頁即多線程。原因很簡單,進程的開銷很昂貴,一個網頁一個進程過於奢侈,用多線程是比較合適的方法。

而Google的Chrome瀏覽器則不同,採用一個網頁一個進程的方式,當你使用Chrome大概四五十個網頁時,任務管理器裏就真的多出了四五十個進程。相當耗費內存,你甚至可以在Chrome中找到一個自帶的任務管理器,用於管理控制Chrome上開啓的多個進程,包括,瀏覽器本體的功能,插件進程,網頁進程,GPU進程等,信息包括CPU使用情況,內存所佔空間以及進程ID。

這樣看似非常耗費內存資源的方式卻大大提高了Chrome的使用體驗,現代瀏覽器因爲網頁的請求資源增加,插件增加,js以及其他功能需要大量資源,單進程運行一個網頁已經比較喫力,讓單進程運行多個網頁更是容易出現崩潰。 如果你使用firefox瀏覽器打開數十個標籤,你會發現在標籤之間切換開始變得喫力,一旦某個網頁出現崩潰,數十個網頁將同樣崩潰,這就形如上述遊戲平臺中出現的情況,同一進程中的某個線程崩潰,將連帶影響該進程中所有線程。

而Chrome就不會。一個網頁崩了,其他標籤仍能正常瀏覽。因此現在越來越多的人喜歡同一瀏覽器上開數十個網頁,這在以前IE普遍使用的年代是不可想象的。多開幾個網頁,你就有全線崩潰的危險。

關於Chrome是如何協調多線程,多進程,CPU,GPU和內存使用的細節具體可以參考:
[譯]官方圖解:Chrome 快是有原因的,現代瀏覽器的多進程架構!

令人興奮的是,在此基礎上,Google可以像開發操作系統一樣開發Chrome的功能,更多方便的小插件,瀏覽器的新性能,可以以應用程序的形式在Chrome上實現。
在這裏插入圖片描述

線程生命週期
二、線程共享的環境包括
進程代碼段、進程的公有數據(利用這些共享的數據,線程很容易的實現相互之間的通訊)、進程打開的文件描述符、信號的處理器、進程的當前目錄和進程用戶ID與進程組ID。

進程擁有這許多共性的同時,還擁有自己的個性。有了這些個性,線程才能實現併發性。這些個性包括:

1.線程ID
每個線程都有自己的線程ID,這個ID在本進程中是唯一的。進程用此來標識線程。

2.寄存器組的值
由於線程間是併發運行的,每個線程有自己不同的運行線索,當從一個線程切換到另一個線程上時,必須將原有的線程的寄存器集合的狀態保存,以便將來該線程在被重新切換到時能得以恢復。

3.線程的堆棧
堆棧是保證線程獨立運行所必須的。
線程函數可以調用函數,而被調用函數中又是可以層層嵌套的,所以線程必須擁有自己的函數堆棧, 使得函數調用可以正常執行,不受其他線程的影響。

4.錯誤返回碼
由於同一個進程中有很多個線程在同時運行,可能某個線程進行系統調用
後設置了errno值,而在該 線程還沒有處理這個錯誤,另外一個線程就在此時
被調度器投入運行,這樣錯誤值就有可能被修改。
所以,不同的線程應該擁有自己的錯誤返回碼變量。

5.線程的信號屏蔽碼
由於每個線程所感興趣的信號不同,所以線程的信號屏蔽碼應該由線程自己管理。但所有的線程都共享同樣的信號處理器。

6.線程的優先級
由於線程需要像進程那樣能夠被調度,那麼就必須要有可供調度使用的參數,這個參數就是線程的優先級。

涉及多線程程序涉及的時候經常會出現一些令人難以思議的事情,用堆和棧分配一個變量可能在以後的執行中產生意想不到的結果,而這個結果的表現就是內存的非法被訪問,導致內存的內容被更改。

理解這個現象的兩個基本概念是:在一個進程的線程共享堆區,而進程中的線程各自維持自己堆棧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章