1. 程式人生 > >萬字長文帶你還原程序和執行緒

萬字長文帶你還原程序和執行緒

我們平常說的程序和執行緒更多的是基於程式語言的角度來說的,那麼你真的瞭解什麼是執行緒和程序嗎?那麼我們就從作業系統的角度來了解一下什麼是程序和執行緒。

程序

作業系統中最核心的概念就是 程序,程序是對正在執行中的程式的一個抽象。作業系統的其他所有內容都是圍繞著程序展開的。程序是作業系統提供的最古老也是最重要的概念之一。即使可以使用的 CPU 只有一個,但它們也支援(偽)併發操作。它們會將一個單獨的 CPU 變換為多個虛擬機器的 CPU。沒有程序的抽象,現代作業系統將不復存在。

所有現代的計算機會在同一時刻做很多事情,過去使用計算機的人可能完全無法理解現在這種變化,舉個例子更能說明這一點:首先考慮一個 Web 伺服器,請求都來自於 Web 網頁。當一個請求到達時,伺服器會檢查當前頁是否在快取中,如果是在快取中,就直接把快取中的內容返回。如果快取中沒有的話,那麼請求就會交給磁碟來處理。但是,從 CPU 的角度來看,磁碟請求需要更長的時間,因為磁碟請求會很慢。當硬碟請求完成時,更多其他請求才會進入。如果有多個磁碟的話,可以在第一個請求完成前就可以連續的對其他磁碟發出部分或全部請求。很顯然,這是一種並發現象,需要有併發控制條件來控制並發現象。

現在考慮只有一個使用者的 PC。當系統啟動時,許多程序也在後臺啟動,使用者通常不知道這些程序的啟動,試想一下,當你自己的計算機啟動的時候,你能知道哪些程序是需要啟動的麼?這些後臺程序可能是一個需要輸入電子郵件的電子郵件程序,或者是一個計算機病毒查殺程序來週期性的更新病毒庫。某個使用者程序可能會在所有使用者上網的時候列印檔案以及燒錄 CD-ROM,這些活動都需要管理。於是一個支援多程序的多道程式系統就會顯得很有必要了。

在許多多道程式系統中,CPU 會在程序間快速切換,使每個程式執行幾十或者幾百毫秒。然而,嚴格意義來說,在某一個瞬間,CPU 只能執行一個程序,然而我們如果把時間定位為 1 秒內的話,它可能執行多個程序。這樣就會讓我們產生並行

的錯覺。有時候人們說的 偽並行(pseudoparallelism) 就是這種情況,以此來區分多處理器系統(該系統由兩個或多個 CPU 來共享同一個實體記憶體)

再來詳細解釋一下偽並行:偽並行是指單核或多核處理器同時執行多個程序,從而使程式更快。 通過以非常有限的時間間隔在程式之間快速切換CPU,因此會產生並行感。 缺點是時間可能分配給下一個程序,也可能不分配給下一個程序。

我們很難對多個並行程序進行跟蹤,因此,在經過多年的努力後,作業系統的設計者開發了用於描述並行的一種概念模型(順序程序),使得並行更加容易理解和分析,對該模型的探討,也是本篇文章的主題

程序模型

在程序模型中,所有計算機上執行的軟體,通常也包括作業系統,被組織為若干順序程序(sequential processes)

,簡稱為 程序(process) 。一個程序就是一個正在執行的程式的例項,程序也包括程式計數器、暫存器和變數的當前值。從概念上來說,每個程序都有各自的虛擬 CPU,但是實際情況是 CPU 會在各個程序之間進行來回切換。

如上圖所示,這是一個具有 4 個程式的多道處理程式,在程序不斷切換的過程中,程式計數器也在不同的變化。

在上圖中,這 4 道程式被抽象為 4 個擁有各自控制流程(即每個自己的程式計數器)的程序,並且每個程式都獨立的執行。當然,實際上只有一個物理程式計數器,每個程式要執行時,其邏輯程式計數器會裝載到物理程式計數器中。當程式執行結束後,其物理程式計數器就會是真正的程式計數器,然後再把它放回程序的邏輯計數器中。

從下圖我們可以看到,在觀察足夠長的一段時間後,所有的程序都運行了,但在任何一個給定的瞬間僅有一個程序真正執行。

在我們接下來的討論中,我們假設只有一個 CPU 的情形。當然,這個假設通常放到現在不會存在了,因為新的晶片通常是多核晶片,包含 2 個、4 個或更多的 CPU。但是現在,一次只考慮一個 CPU 會便於我們分析問題。因此,當我們說一個 CPU 只能真正一次執行一個程序的時候,即使有 2 個核(或 CPU),每一個核也只能一次執行一個執行緒。

由於 CPU 會在各個程序之間來回快速切換,所以每個程序在 CPU 中的執行時間是無法確定的。並且當同一個程序再次在 CPU 中執行時,其在 CPU 內部的執行時間往往也是不固定的。程序和程式之間的區別是非常微妙的,但是通過一個例子可以讓你加以區分:想想一位會做飯的電腦科學家正在為他的女兒製作生日蛋糕。他有做生日蛋糕的食譜,廚房裡有所需的原諒:麵粉、雞蛋、糖、香草汁等。在這個比喻中,做蛋糕的食譜就是程式、電腦科學家就是 CPU、而做蛋糕的各種原諒都是輸入資料。程序就是科學家閱讀食譜、取來各種原料以及烘焙蛋糕等一系例了動作的總和。

現在假設科學家的兒子跑過來告訴他,說他的頭被蜜蜂蜇了一下,那麼此時科學家會記錄出來他做蛋糕這個過程到了哪一步,然後拿出急救手冊,按照上面的步驟給他兒子實施救助。這裡,會涉及到程序之間的切換,科學家(CPU)會從做蛋糕(程序)切換到實施醫療救助(另一個程序)。等待傷口處理完畢後,科學家會回到剛剛記錄做蛋糕的那一步,繼續製作。

這裡的關鍵思想是認識到一個程序所需的條件,程序是某一類特定活動的總和,它有程式、輸入輸出以及狀態。單個處理器可以被若干程序共享,它使用某種排程演算法決定何時停止一個程序的工作,並轉而為另外一個程序提供服務。另外需要注意的是,如果一個程序運行了兩遍,則被認為是兩個程序。

程序的建立

作業系統需要一些方式來建立程序。在非常簡單的系統中,或者作業系統被設計用來執行單個應用程式(例如微波爐中的控制器),可能在系統啟動時,也需要所有的程序一起啟動。但在通用系統中,然而,需要有某種方法在執行時按需建立或銷燬程序,現在需要考察這個問題,下面是建立程序的方式

  • 系統初始化
  • 正在執行的程式執行了建立程序的系統呼叫(比如 fork)
  • 使用者請求建立一個新程序
  • 初始化一個批處理工作

啟動作業系統時,通常會建立若干個程序。其中有些是前臺程序(numerous processes),也就是同用戶進行互動並替他們完成工作的程序。一些執行在後臺,並不與特定的使用者進行互動,但是後臺程序也有特定的功能。例如,設計一個後臺程序來接收發來的電子郵件,這個程序大部分的時間都在休眠但是隻要郵件到來後這個程序就會被喚醒。還可以設計一個後臺程序來接收對該計算機上網頁的傳入請求,在請求到達的程序喚醒來處理網頁的傳入請求。程序執行在後臺用來處理一些活動像是 e-mail,web 網頁,新聞,列印等等被稱為 守護程序(daemons)。大型系統會有很多守護程序。在 UNIX 中,ps 程式可以列出正在執行的程序, 在 Windows 中,可以使用工作管理員。

除了在啟動階段建立程序之外,一些新的程序也可以在後面建立。通常,一個正在執行的程序會發出系統呼叫以建立一個或多個新程序來幫助其完成工作。當可以輕鬆地根據幾個相關但相互獨立的互動過程來共同完成一項工作時,建立新程序就顯得特別有用。例如,如果有大量的資料需要經過網路調取並進行順序處理,那麼建立一個程序讀資料,並把資料放到共享緩衝區中,而讓第二個程序取走並正確處理會比較容易些。在多處理器中,讓每個程序執行在不同的 CPU 上也可以使工作做的更快。

在許多互動式系統中,輸入一個命令或者雙擊圖示就可以啟動程式,以上任意一種操作都可以選擇開啟一個新的程序,在基本的 UNIX 系統中執行 X,新程序將接管啟動它的視窗。在 Windows 中啟動程序時,它一般沒有視窗,但是它可以建立一個或多個視窗。每個視窗都可以執行程序。通過滑鼠或者命令就可以切換視窗並與程序進行互動。

互動式系統是以人與計算機之間大量互動為特徵的計算機系統,比如遊戲、web瀏覽器,IDE 等整合開發環境。

最後一種建立程序的情形會在大型機的批處理系統中應用。使用者在這種系統中提交批處理作業。當作業系統決定它有資源來執行另一個任務時,它將建立一個新程序並從其中的輸入佇列中執行下一個作業。

從技術上講,在所有這些情況下,讓現有流程執行流程是通過建立系統呼叫來建立新流程的。該程序可能是正在執行的使用者程序,是從鍵盤或滑鼠呼叫的系統程序或批處理程式。這些就是系統呼叫建立新程序的過程。該系統呼叫告訴作業系統建立一個新程序,並直接或間接指示在其中執行哪個程式。

在 UNIX 中,這僅有一個系統呼叫來建立一個新的程序,這個系統呼叫就是 fork。這個呼叫會建立一個與呼叫程序相關的副本。在 fork 後,一個父程序和子程序會有相同的記憶體映像,相同的環境字串和相同的開啟檔案。通常,子程序會執行 execve 或者一個簡單的系統呼叫來改變記憶體映像並執行一個新的程式。例如,當一個使用者在 shell 中輸出 sort 命令時,shell 會 fork 一個子程序然後子程序去執行 sort 命令。這兩步過程的原因是允許子程序在 fork 之後但在 execve 之前操作其檔案描述符,以完成標準輸入,標準輸出和標準錯誤的重定向。

在 Windows 中,情況正相反,一個簡單的 Win32 功能呼叫 CreateProcess,會處理流程建立並將正確的程式載入到新的程序中。這個呼叫會有 10 個引數,包括了需要執行的程式、輸入給程式的命令列引數、各種安全屬性、有關開啟的檔案是否繼承控制位、優先順序資訊、程序所需要建立的視窗規格以及指向一個結構的指標,在該結構中新建立程序的資訊被返回給呼叫者。除了 CreateProcess Win 32 中大概有 100 個其他的函式用於處理程序的管理,同步以及相關的事務。下面是 UNIX 作業系統和 Windows 作業系統系統呼叫的對比

UNIX Win32 說明
fork CreateProcess 建立一個新程序
waitpid WaitForSingleObject 等待一個程序退出
execve none CraeteProcess = fork + servvice
exit ExitProcess 終止執行
open CreateFile 建立一個檔案或開啟一個已有的檔案
close CloseHandle 關閉檔案
read ReadFile 從單個檔案中讀取資料
write WriteFile 向單個檔案寫資料
lseek SetFilePointer 移動檔案指標
stat GetFileAttributesEx 獲得不同的檔案屬性
mkdir CreateDirectory 建立一個新的目錄
rmdir RemoveDirectory 移除一個空的目錄
link none Win32 不支援 link
unlink DeleteFile 銷燬一個已有的檔案
mount none Win32 不支援 mount
umount none Win32 不支援 mount,所以也不支援mount
chdir SetCurrentDirectory 切換當前工作目錄
chmod none Win32 不支援安全
kill none Win32 不支援訊號
time GetLocalTime 獲取當前時間

在 UNIX 和 Windows 中,程序建立之後,父程序和子程序有各自不同的地址空間。如果其中某個程序在其地址空間中修改了一個詞,這個修改將對另一個程序不可見。在 UNIX 中,子程序的地址空間是父程序的一個拷貝,但是確是兩個不同的地址空間;不可寫的記憶體區域是共享的。某些 UNIX 實現是正文區在兩者之間共享,因為它不能被修改。或者,子程序共享父程序的所有記憶體,但是這種情況下記憶體通過 寫時複製(copy-on-write) 共享,這意味著一旦兩者之一想要修改部分記憶體,則這塊記憶體首先被明確的複製,以確保修改發生在私有記憶體區域。再次強調,可寫的記憶體是不能被共享的。但是,對於一個新建立的程序來說,確實有可能共享建立者的資源,比如可以共享開啟的檔案呢。在 Windows 中,從一開始父程序的地址空間和子程序的地址空間就是不同的。

程序的終止

程序在建立之後,它就開始執行並做完成任務。然而,沒有什麼事兒是永不停歇的,包括程序也一樣。程序早晚會發生終止,但是通常是由於以下情況觸發的

  • 正常退出(自願的)
  • 錯誤退出(自願的)
  • 嚴重錯誤(非自願的)
  • 被其他程序殺死(非自願的)

多數程序是由於完成了工作而終止。當編譯器完成了所給定程式的編譯之後,編譯器會執行一個系統呼叫告訴作業系統它完成了工作。這個呼叫在 UNIX 中是 exit ,在 Windows 中是 ExitProcess。面向螢幕中的軟體也支援自願終止操作。字處理軟體、Internet 瀏覽器和類似的程式中總有一個供使用者點選的圖示或選單項,用來通知程序刪除它鎖開啟的任何臨時檔案,然後終止。

程序發生終止的第二個原因是發現嚴重錯誤,例如,如果使用者執行如下命令

cc foo.c    

為了能夠編譯 foo.c 但是該檔案不存在,於是編譯器就會發出聲明並退出。在給出了錯誤引數時,面向螢幕的互動式程序通常並不會直接退出,因為這從使用者的角度來說並不合理,使用者需要知道發生了什麼並想要進行重試,所以這時候應用程式通常會彈出一個對話方塊告知使用者發生了系統錯誤,是需要重試還是退出。

程序終止的第三個原因是由程序引起的錯誤,通常是由於程式中的錯誤所導致的。例如,執行了一條非法指令,引用不存在的記憶體,或者除數是 0 等。在有些系統比如 UNIX 中,程序可以通知作業系統,它希望自行處理某種型別的錯誤,在這類錯誤中,程序會收到訊號(中斷),而不是在這類錯誤出現時直接終止程序。

第四個終止程序的原因是,某個程序執行系統呼叫告訴作業系統殺死某個程序。在 UNIX 中,這個系統呼叫是 kill。在 Win32 中對應的函式是 TerminateProcess(注意不是系統呼叫),

程序的層次結構

在一些系統中,當一個程序建立了其他程序後,父程序和子程序就會以某種方式進行關聯。子程序它自己就會建立更多程序,從而形成一個程序層次結構。

在 UNIX 中,程序和它的所有子程序以及後裔共同組成一個程序組。當用戶從鍵盤中發出一個訊號後,該訊號被髮送給當前與鍵盤相關的程序組中的所有成員(它們通常是在當前視窗建立的所有活動程序)。每個程序可以分別捕獲該訊號、忽略該訊號或採取預設的動作,即被訊號 kill 掉。

這裡有另一個例子,可以用來說明層次的作用,考慮 UNIX 在啟動時如何初始化自己。一個稱為 init 的特殊程序出現在啟動映像中 。當 init 程序開始執行時,它會讀取一個檔案,檔案會告訴它有多少個終端。然後為每個終端建立一個新程序。這些程序等待使用者登入。如果登入成功,該登入程序就執行一個 shell 來等待接收使用者輸入指令,這些命令可能會啟動更多的程序,以此類推。因此,整個作業系統中所有的程序都隸屬於一個單個以 init 為根的程序樹。

相反,Windows 中沒有程序層次的概念,Windows 中所有程序都是平等的,唯一類似於層次結構的是在建立程序的時候,父程序得到一個特別的令牌(稱為控制代碼),該控制代碼可以用來控制子程序。然而,這個令牌可能也會移交給別的作業系統,這樣就不存在層次結構了。而在 UNIX 中,程序不能剝奪其子程序的 程序權。(這樣看來,還是 Windows 比較)。

程序狀態

儘管每個程序是一個獨立的實體,有其自己的程式計數器和內部狀態,但是,程序之間仍然需要相互作用。一個程序的結果可以作為另一個程序的輸入,在 shell 命令中

cat chapter1 chapter2 chapter3 | grep tree

第一個程序是 cat,將三個檔案級聯並輸出。第二個程序是 grep,它從輸入中選擇具有包含關鍵字 tree 的內容,根據這兩個程序的相對速度(這取決於兩個程式的相對複雜度和各自所分配到的 CPU 時間片),可能會發生下面這種情況,grep 準備就緒開始執行,但是輸入程序還沒有完成,於是必須阻塞 grep 程序,直到輸入完畢。

當一個程序在邏輯上無法繼續執行時,它就會被阻塞,比如程序在等待可以使用的輸入。還有可能是這樣的情況:由於作業系統已經決定暫時將 CPU 分配給另一個程序,因此準備就緒的程序也有可能會終止。導致這兩種情況的因素是完全不同的:

  • 第一種情況的本質是程序的掛起,你必須先輸入使用者的命令列,才能執行接下來的操作。
  • 第二種情況完全是作業系統的技術問題:沒有足夠的 CPU 來為每個程序提供自己私有的處理器。

當一個程序開始執行時,它可能會經歷下面這幾種狀態

圖中會涉及三種狀態

  1. 執行態,執行態指的就是程序實際佔用 CPU 執行時
  2. 就緒態,就緒態指的是可執行,但因為其他程序正在執行而終止
  3. 阻塞態,除非某種外部事件發生,否則程序不能執行

邏輯上來說,執行態和就緒態是很相似的。這兩種情況下都表示程序可執行,但是第二種情況沒有獲得 CPU 時間分片。第三種狀態與前兩種狀態不同是因為這個程序不能執行,CPU 空閒或者沒有任何事情去做的時候也不能執行。

三種狀態會涉及四種狀態間的切換,在作業系統發現程序不能繼續執行時會發生狀態1的輪轉,在某些系統中程序執行系統呼叫,例如 pause,來獲取一個阻塞的狀態。在其他系統中包括 UNIX,當程序從管道或特殊檔案(例如終端)中讀取沒有可用的輸入時,該程序會被自動終止。

轉換 2 和轉換 3 都是由程序排程程式(作業系統的一部分)引起的,而程序甚至不知道它們。轉換 2 的出現說明程序排程器認定當前程序已經運行了足夠長的時間,是時候讓其他程序執行 CPU 時間片了。當所有其他程序都執行過後,這時候該是讓第一個程序重新獲得 CPU 時間片的時候了,就會發生轉換 3。

程式排程指的是,決定哪個程序優先被執行和執行多久,這是很重要的一點。已經設計出許多演算法來嘗試平衡系統整體效率與各個流程之間的競爭需求。

當程序等待的一個外部事件發生時(如一些輸入到達),則發生轉換 4。如果此時沒有其他程序在執行,則立刻觸發轉換 3,該程序便開始執行,否則該程序會處於就緒階段,等待 CPU 空閒後再輪到它執行。

使用程序模型,會讓我們更容易理解作業系統內部的工作狀況。一些程序執行執行使用者鍵入的命令的程式。另一些程序是系統的一部分,它們的任務是完成下列一些工作:比如,執行檔案服務請求,管理磁碟驅動和磁帶機的執行細節等。當發生一個磁碟中斷時,系統會做出決定,停止運行當前程序,轉而執行磁碟程序,該程序在此之前因等待中斷而處於阻塞態。這樣可以不再考慮中斷,而只是考慮使用者程序、磁碟程序、終端程序等。這些程序在等待時總是處於阻塞態。在已經讀如磁碟或者輸入字元後,等待它們的程序就被解除阻塞,併成為可排程執行的程序。

從上面的觀點引入了下面的模型

作業系統最底層的就是排程程式,在它上面有許多程序。所有關於中斷處理、啟動程序和停止程序的具體細節都隱藏在排程程式中。事實上,排程程式只是一段非常小的程式。

程序的實現

我們之前提過,作業系統為了執行程序間的切換,會維護著一張表格,即 程序表(process table)。每個程序佔用一個程序表項。該表項包含了程序狀態的重要資訊,包括程式計數器、堆疊指標、記憶體分配狀況、所開啟檔案的狀態、賬號和排程資訊,以及其他在程序由執行態轉換到就緒態或阻塞態時所必須儲存的資訊,從而保證該程序隨後能再次啟動,就像從未被中斷過一樣。

下面展示了一個典型系統中的關鍵欄位

第一列內容與程序管理有關,第二列內容與 儲存管理有關,第三列內容與檔案管理有關。

儲存管理的 text segment 、 data segment、stack segment 更多瞭解見下面這篇文章

程式設計師需要了解的硬核知識之組合語言(全)

現在我們應該對程序表有個大致的瞭解了,就可以在對單個 CPU 上如何執行多個順序程序的錯覺做更多的解釋。與每一 I/O 類相關聯的是一個稱作 中斷向量(interrupt vector) 的位置(靠近記憶體底部的固定區域)。它包含中斷服務程式的入口地址。假設當一個磁碟中斷髮生時,使用者程序 3 正在執行,則中斷硬體將程式計數器、程式狀態字、有時還有一個或多個暫存器壓入堆疊,計算機隨即跳轉到中斷向量所指示的地址。這就是硬體所做的事情。然後軟體就隨即接管一切剩餘的工作。

所有的中斷都從儲存暫存器開始,對於當前程序而言,通常是儲存在程序表項中。隨後,會從堆疊中刪除由中斷硬體機制存入堆疊的那部分資訊,並將堆疊指標指向一個由程序處理程式所使用的臨時堆疊。一些諸如儲存暫存器的值和設定堆疊指標等操作,無法用 C 語言等高階語言描述,所以這些操作通過一個短小的組合語言來完成,通常可以供所有的中斷來使用,無論中斷是怎樣引起的,其儲存暫存器的工作是一樣的。

當中斷結束後,作業系統會呼叫一個 C 程式來處理中斷剩下的工作。在完成剩下的工作後,會使某些程序就緒,接著呼叫排程程式,決定隨後執行哪個程序。隨後將控制權轉移給一段組合語言程式碼,為當前的程序裝入暫存器值以及記憶體對映並啟動該程序執行,下面顯示了中斷處理和排程的過程。

  1. 硬體壓入堆疊程式計數器等

  2. 硬體從中斷向量裝入新的程式計數器

  3. 組合語言過程儲存暫存器的值

  4. 組合語言過程設定新的堆疊

  5. C 中斷伺服器執行(典型的讀和快取寫入)

  6. 排程器決定下面哪個程式先執行

  7. C 過程返回至彙編程式碼

  8. 組合語言過程開始執行新的當前程序

一個程序在執行過程中可能被中斷數千次,但關鍵每次中斷後,被中斷的程序都返回到與中斷髮生前完全相同的狀態。

執行緒

在傳統的作業系統中,每個程序都有一個地址空間和一個控制執行緒。事實上,這是大部分程序的定義。不過,在許多情況下,經常存在在同一地址空間中執行多個控制執行緒的情形,這些執行緒就像是分離的程序。下面我們就著重探討一下什麼是執行緒

執行緒的使用

或許這個疑問也是你的疑問,為什麼要在程序的基礎上再建立一個執行緒的概念,準確的說,這其實是程序模型和執行緒模型的討論,回答這個問題,可能需要分三步來回答

  • 多執行緒之間會共享同一塊地址空間和所有可用資料的能力,這是程序所不具備的
  • 執行緒要比程序更輕量級,由於執行緒更輕,所以它比程序更容易建立,也更容易撤銷。在許多系統中,建立一個執行緒要比建立一個程序快 10 - 100 倍。
  • 第三個原因可能是效能方面的探討,如果多個執行緒都是 CPU 密集型的,那麼並不能獲得性能上的增強,但是如果存在著大量的計算和大量的 I/O 處理,擁有多個執行緒能在這些活動中彼此重疊進行,從而會加快應用程式的執行速度

多執行緒解決方案

現在考慮一個執行緒使用的例子:一個全球資訊網伺服器,對頁面的請求傳送給伺服器,而所請求的頁面傳送回客戶端。在多數 web 站點上,某些頁面較其他頁面相比有更多的訪問。例如,索尼的主頁比任何一個照相機詳情介紹頁面具有更多的訪問,Web 伺服器可以把獲得大量訪問的頁面集合儲存在記憶體中,避免到磁碟去調入這些頁面,從而改善效能。這種頁面的集合稱為 快取記憶體(cache),快取記憶體也應用在許多場合中,比如說 CPU 快取。

上面是一個 web 伺服器的組織方式,一個叫做 排程執行緒(dispatcher thread) 的執行緒從網路中讀入工作請求,在排程執行緒檢查完請求後,它會選擇一個空閒的(阻塞的)工作執行緒來處理請求,通常是將訊息的指標寫入到每個執行緒關聯的特殊字中。然後排程執行緒會喚醒正在睡眠中的工作執行緒,把工作執行緒的狀態從阻塞態變為就緒態。

當工作執行緒啟動後,它會檢查請求是否在 web 頁面的快取記憶體中存在,這個快取記憶體是所有執行緒都可以訪問的。如果快取記憶體不存在這個 web 頁面的話,它會呼叫一個 read 操作從磁碟中獲取頁面並且阻塞執行緒直到磁碟操作完成。當執行緒阻塞在硬碟操作的期間,為了完成更多的工作,排程執行緒可能挑選另一個執行緒執行,也可能把另一個當前就緒的工作執行緒投入執行。

這種模型允許將伺服器編寫為順序執行緒的集合,在分派執行緒的程式中包含一個死迴圈,該迴圈用來獲得工作請求並且把請求派給工作執行緒。每個工作執行緒的程式碼包含一個從排程執行緒接收的請求,並且檢查 web 快取記憶體中是否存在所需頁面,如果有,直接把該頁面返回給客戶,接著工作執行緒阻塞,等待一個新請求的到達。如果沒有,工作執行緒就從磁碟調入該頁面,將該頁面返回給客戶機,然後工作執行緒阻塞,等待一個新請求。

下面是排程執行緒和工作執行緒的程式碼,這裡假設 TRUE 為常數 1 ,buf 和 page 分別是儲存工作請求和 Web 頁面的相應結構。

排程執行緒的大致邏輯

while(TRUE){
  get_next_request(&buf);
  handoff_work(&buf);
}

工作執行緒的大致邏輯

while(TRUE){
  wait_for_work(&buf);
  look_for_page_in_cache(&buf,&page);
  if(page_not_in_cache(&page)){
    read_page_from_disk(&buf,&page);
  }
  return _page(&page);
}

單執行緒解決方案

現在考慮沒有多執行緒的情況下,如何編寫 Web 伺服器。我們很容易的就想象為單個執行緒了,Web 伺服器的主迴圈獲取請求並檢查請求,並爭取在下一個請求之前完成工作。在等待磁碟操作時,伺服器空轉,並且不處理任何到來的其他請求。結果會導致每秒中只有很少的請求被處理,所以這個例子能夠說明多執行緒提高了程式的並行性並提高了程式的效能。

狀態機解決方案

到現在為止,我們已經有了兩種解決方案,單執行緒解決方案和多執行緒解決方案,其實還有一種解決方案就是 狀態機解決方案,它的流程如下

如果目前只有一個非阻塞版本的 read 系統呼叫可以使用,那麼當請求到達伺服器時,這個唯一的 read 呼叫的執行緒會進行檢查,如果能夠從快取記憶體中得到響應,那麼直接返回,如果不能,則啟動一個非阻塞的磁碟操作

伺服器在表中記錄當前請求的狀態,然後進入並獲取下一個事件,緊接著下一個事件可能就是一個新工作的請求或是磁碟對先前操作的回答。如果是新工作的請求,那麼就開始處理請求。如果是磁碟的響應,就從表中取出對應的狀態資訊進行處理。對於非阻塞式磁碟 I/O 而言,這種響應一般都是訊號中斷響應。

每次伺服器從某個請求工作的狀態切換到另一個狀態時,都必須顯示的儲存或者重新裝入相應的計算狀態。這裡,每個計算都有一個被儲存的狀態,存在一個會發生且使得相關狀態發生改變的事件集合,我們把這類設計稱為有限狀態機(finite-state machine),有限狀態機杯廣泛的應用在電腦科學中。

這三種解決方案各有各的特性,多執行緒使得順序程序的思想得以保留下來,並且實現了並行性,但是順序程序會阻塞系統呼叫;單執行緒伺服器保留了阻塞系統的簡易性,但是卻放棄了效能。有限狀態機的處理方法運用了非阻塞呼叫和中斷,通過並行實現了高效能,但是給程式設計增加了困難。

模型 特性
單執行緒 無並行性,效能較差,阻塞系統呼叫
多執行緒 有並行性,阻塞系統呼叫
有限狀態機 並行性,非阻塞系統呼叫、中斷

經典的執行緒模型

理解程序的另一個角度是,用某種方法把相關的資源集中在一起。程序有存放程式正文和資料以及其他資源的地址空間。這些資源包括開啟的檔案、子程序、即將發生的定時器、訊號處理程式、賬號資訊等。把這些資訊放在程序中會比較容易管理。

另一個概念是,程序中擁有一個執行的執行緒,通常簡寫為 執行緒(thread)。執行緒會有程式計數器,用來記錄接著要執行哪一條指令;執行緒還擁有暫存器,用來儲存執行緒當前正在使用的變數;執行緒還會有堆疊,用來記錄程式的執行路徑。儘管執行緒必須在某個程序中執行,但是程序和執行緒完完全全是兩個不同的概,並且他們可以分開處理。程序用於把資源集中在一起,而執行緒則是 CPU 上排程執行的實體。

執行緒給程序模型增加了一項內容,即在同一個程序中,允許彼此之間有較大的獨立性且互不干擾。在一個程序中並行執行多個執行緒類似於在一臺計算機上執行多個程序。在多個執行緒中,多個執行緒共享同一地址空間和其他資源。在多個程序中,程序共享實體記憶體、磁碟、印表機和其他資源。因為執行緒會包含有一些程序的屬性,所以執行緒被稱為輕量的程序(lightweight processes)多執行緒(multithreading)一詞還用於描述在同一程序中多個執行緒的情況。

下圖我們可以看到三個傳統的程序,每個程序有自己的地址空間和單個控制執行緒。每個執行緒都在不同的地址空間中執行

下圖中,我們可以看到有一個程序三個執行緒的情況。每個執行緒都在相同的地址空間中執行。

當多個執行緒在單 CPU 系統中執行時,執行緒輪流執行,在對程序進行描述的過程中,我們知道了程序的多道程式是如何工作的。通過在多個程序之間來回切換,系統製造了不同的順序程序並行執行的假象。多執行緒的工作方式也是類似。CPU 線上程之間來回切換,系統製造了不同的順序程序並行執行的假象。

但是程序中的不同執行緒沒有不同程序間較強的獨立性。同一個程序中的所有執行緒都會有完全一樣的地址空間,這意味著它們也共享同樣的全域性變數。由於每個執行緒都可以訪問程序地址空間內每個記憶體地址,因此一個執行緒可以讀取、寫入甚至擦除另一個執行緒的堆疊。執行緒之間為什麼沒有保護呢?既不可能也沒有必要。這與不同程序間是有差別的,不同的程序會來自不同的使用者,它們彼此之間可能有敵意,因為彼此不同的程序間會互相爭搶資源。而一個程序總是由一個使用者所擁有,所以作業系統設計者把執行緒設計出來是為了讓他們 相互合作而不是相互鬥爭的。執行緒之間除了共享同一記憶體空間外,還具有如下不同的內容

上圖左邊的是同一個程序中每個執行緒共享的內容,上圖右邊是每個執行緒中的內容。也就是說左邊的列表是程序的屬性,右邊的列表是執行緒的屬性。

執行緒概念試圖實現的是,共享一組資源的多個執行緒的執行能力,以便這些執行緒可以為完成某一任務而共同工作。和程序一樣,執行緒可以處於下面這幾種狀態:執行中、阻塞、就緒和終止(程序圖中沒有畫)。正在執行的執行緒擁有 CPU 時間片並且狀態是執行中。一個被阻塞的執行緒會等待某個釋放它的事件。例如,當一個執行緒執行從鍵盤讀入資料的系統呼叫時,該執行緒就被阻塞直到有輸入為止。執行緒通常會被阻塞,直到它等待某個外部事件的發生或者有其他執行緒來釋放它。執行緒之間的狀態轉換和程序之間的狀態轉換是一樣的。

每個執行緒都會有自己的堆疊,如下圖所示

在多執行緒情況下,程序通常會從當前的某個單執行緒開始,然後這個執行緒通過呼叫一個庫函式(比如 thread_create)建立新的執行緒。執行緒建立的函式會要求指定新建立執行緒的名稱。建立的執行緒通常都返回一個執行緒識別符號,該識別符號就是新執行緒的名字。

當一個執行緒完成工作後,可以通過呼叫一個函式(比如 thread_exit)來退出。緊接著執行緒消失,狀態變為死亡,不能再進行排程。在某些執行緒的執行過程中,可以通過呼叫函式例如 thread_join ,表示一個執行緒可以等待另一個執行緒退出。這個過程阻塞呼叫執行緒直到等待特定的執行緒退出。在這種情況下,執行緒的建立和終止非常類似於程序的建立和終止。

另一個常見的執行緒是呼叫 thread_yield,它允許執行緒自動放棄 CPU 從而讓另一個執行緒執行。這樣一個呼叫還是很重要的,因為不同於程序,執行緒是無法利用時鐘中斷強制讓執行緒讓出 CPU 的。所以設法讓執行緒的行為 高大上一些還是比較重要的,並且隨著時間的推移讓執行緒讓出 CPU,以便讓其他執行緒獲得執行的機會。執行緒會帶來很多的問題,必須要在設計時考慮全面。

POSIX 執行緒

為了使編寫可移植執行緒程式成為可能,IEEE 在 IEEE 標準 1003.1c 中定義了執行緒標準。執行緒包被定義為 Pthreads。大部分的 UNIX 系統支援它。這個標準定義了 60 多種功能呼叫,一一列舉不太現實,下面為你列舉了一些常用的系統呼叫。

POSIX執行緒(通常稱為pthreads)是一種獨立於語言而存在的執行模型,以及並行執行模型。它允許程式控制時間上重疊的多個不同的工作流程。每個工作流程都稱為一個執行緒,可以通過呼叫POSIX Threads API來實現對這些流程的建立和控制。可以把它理解為執行緒的標準。

POSIX Threads 的實現在許多類似且符合POSIX的作業系統上可用,例如 FreeBSD、NetBSD、OpenBSD、Linux、macOS、Android、Solaris,它在現有 Windows API 之上實現了pthread。

IEEE 是世界上最大的技術專業組織,致力於為人類的利益而發展技術。

執行緒呼叫 描述
pthread_create 建立一個新執行緒
pthread_exit 結束呼叫的執行緒
pthread_join 等待一個特定的執行緒退出
pthread_yield 釋放 CPU 來執行另外一個執行緒
pthread_attr_init 建立並初始化一個執行緒的屬性結構
pthread_attr_destory 刪除一個執行緒的屬性結構

所有的 Pthreads 都有特定的屬性,每一個都含有識別符號、一組暫存器(包括程式計數器)和一組儲存在結構中的屬性。這個屬性包括堆疊大小、排程引數以及其他執行緒需要的專案。

新的執行緒會通過 pthread_create 建立,新建立的執行緒的識別符號會作為函式值返回。這個呼叫非常像是 UNIX 中的 fork 系統呼叫(除了引數之外),其中執行緒識別符號起著 PID 的作用,這麼做的目的是為了和其他執行緒進行區分。

當執行緒完成指派給他的工作後,會通過 pthread_exit 來終止。這個呼叫會停止執行緒並釋放堆疊。

一般一個執行緒在繼續執行前需要等待另一個執行緒完成它的工作並退出。可以通過 pthread_join 執行緒呼叫來等待別的特定執行緒的終止。而要等待執行緒的執行緒識別符號作為一個引數給出。

有時會出現這種情況:一個執行緒邏輯上沒有阻塞,但感覺上它已經運行了足夠長的時間並且希望給另外一個執行緒機會去執行。這時候可以通過 pthread_yield 來完成。

下面兩個執行緒呼叫是處理屬性的。pthread_attr_init 建立關聯一個執行緒的屬性結構並初始化成預設值,這些值(例如優先順序)可以通過修改屬性結構的值來改變。

最後,pthread_attr_destroy 刪除一個執行緒的結構,釋放它佔用的記憶體。它不會影響呼叫它的執行緒,這些執行緒會一直存在。

為了更好的理解 pthread 是如何工作的,考慮下面這個例子

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUMBER_OF_THREADS 10

void *print_hello_world(vvoid *tid){
  /* 輸出執行緒的識別符號,然後退出 */
  printf("Hello World. Greetings from thread %d\n",tid);
  pthread_exit(NULL);
}

int main(int argc,char *argv[]){
  /* 主程式建立 10 個執行緒,然後退出 */
  pthread_t threads[NUMBER_OF_THREADS];
  int status,i;
    
  for(int i = 0;i < NUMBER_OF_THREADS;i++){
    printf("Main here. Creating thread %d\n",i);
    status = pthread_create(&threads[i], NULL, print_hello_world, (void *)i);
    
    if(status != 0){
      printf("Oops. pthread_create returned error code %d\n",status);
      exit(-1);
    }
  }
  exit(NULL);
}

主執行緒在宣佈它的指責之後,迴圈 NUMBER_OF_THREADS 次,每次建立一個新的執行緒。如果執行緒建立失敗,會打印出一條資訊後退出。在建立完成所有的工作後,主程式退出。

執行緒實現

主要有三種實現,一種是在使用者空間中實現執行緒,一種是在核心空間中實現執行緒,一種是在使用者和核心空間中混合實現執行緒。下面我們分開討論一下

在使用者空間中實現執行緒

第一種方法是把整個執行緒包放在使用者空間中,核心對執行緒一無所知,它不知道執行緒的存在。所有的這類實現都有同樣的通用結構

執行緒在執行時系統之上執行,執行時系統是管理執行緒過程的集合,包括前面提到的四個過程: pthread_create, pthread_exit, pthread_join 和 pthread_yield。

執行時系統(Runtime System) 也叫做執行時環境,該執行時系統提供了程式在其中執行的環境。此環境可能會解決許多問題,包括應用程式記憶體的佈局,程式如何訪問變數,在過程之間傳遞引數的機制,與作業系統的介面等等。編譯器根據特定的執行時系統進行假設以生成正確的程式碼。通常,執行時系統將負責設定和管理堆疊,並且會包含諸如垃圾收集,執行緒或語言內建的其他動態的功能。

在使用者空間管理執行緒時,每個程序需要有其專用的執行緒表(thread table),用來跟蹤該程序中的執行緒。這些表和核心中的程序表類似,不過它僅僅記錄各個執行緒的屬性,如每個執行緒的程式計數器、堆疊指標、暫存器和狀態。該執行緒標由執行時系統統一管理。當一個執行緒轉換到就緒狀態或阻塞狀態時,在該執行緒表中存放重新啟動該執行緒的所有資訊,與核心在程序表中存放的資訊完全一樣。

在使用者空間實現執行緒的優勢

在使用者空間中實現執行緒要比在核心空間中實現執行緒具有這些方面的優勢:考慮如果線上程完成時或者是在呼叫 pthread_yield 時,必要時會程序執行緒切換,然後執行緒的資訊會被儲存在執行時環境所提供的執行緒表中,進而,執行緒排程程式來選擇另外一個需要執行的執行緒。儲存執行緒的狀態和排程程式都是本地過程,所以啟動他們比進行核心呼叫效率更高。因而不需要陷入核心,也就不需要上下文切換,也不需要對記憶體快取記憶體進行重新整理,因為執行緒排程非常便捷,因此效率比較高。

在使用者空間實現執行緒還有一個優勢就是它允許每個程序有自己定製的排程演算法。例如在某些應用程式中,那些具有垃圾收集執行緒的應用程式(知道是誰了吧)就不用擔心自己執行緒會不會在不合適的時候停止,這是一個優勢。使用者執行緒還具有較好的可擴充套件性,因為核心空間中的核心執行緒需要一些表空間和堆疊空間,如果核心執行緒數量比較大,容易造成問題。

在使用者空間實現執行緒的劣勢

儘管在使用者空間實現執行緒會具有一定的效能優勢,但是劣勢還是很明顯的,你如何實現阻塞系統呼叫呢?假設在還沒有任何鍵盤輸入之前,一個執行緒讀取鍵盤,讓執行緒進行系統呼叫是不可能的,因為這會停止所有的執行緒。所以,使用執行緒的一個目標是能夠讓執行緒進行阻塞呼叫,並且要避免被阻塞的執行緒影響其他執行緒。

與阻塞呼叫類似的問題是缺頁中斷問題,實際上,計算機並不會把所有的程式都一次性的放入記憶體中,如果某個程式發生函式呼叫或者跳轉指令到了一條不在記憶體的指令上,就會發生頁面故障,而作業系統將到磁碟上取回這個丟失的指令,這就稱為缺頁故障。而在對所需的指令進行讀入和執行時,相關的程序就會被阻塞。如果只有一個執行緒引起頁面故障,核心由於甚至不知道有執行緒存在,通常會吧整個程序阻塞直到磁碟 I/O 完成為止,儘管其他的執行緒是可以執行的。

在使用者空間實現執行緒的另外一個問題是,如果一個執行緒開始執行,該執行緒所在程序中的其他執行緒都不能執行,除非第一個執行緒自願的放棄 CPU,在一個單程序內部,沒有時鐘中斷,所以不可能使用輪轉排程的方式排程執行緒。除非其他執行緒能夠以自己的意願進入執行時環境,否則排程程式沒有可以排程執行緒的機會。

在核心中實現執行緒

現在我們考慮使用核心來實現執行緒的情況,此時不再需要執行時環境了。另外,每個程序中也沒有執行緒表。相反,在核心中會有用來記錄系統中所有執行緒的執行緒表。當某個執行緒希望建立一個新執行緒或撤銷一個已有執行緒時,它會進行一個系統呼叫,這個系統呼叫通過對執行緒表的更新來完成執行緒建立或銷燬工作。

核心中的執行緒表持有每個執行緒的暫存器、狀態和其他資訊。這些資訊和使用者空間中的執行緒資訊相同,但是位置卻被放在了核心中而不是使用者空間中。另外,核心還維護了一張程序表用來跟蹤系統狀態。

所有能夠阻塞的呼叫都會通過系統呼叫的方式來實現,當一個執行緒阻塞時,核心可以進行選擇,是執行在同一個程序中的另一個執行緒(如果有就緒執行緒的話)還是執行一個另一個程序中的執行緒。但是在使用者實現中,執行時系統始終執行自己的執行緒,直到核心剝奪它的 CPU 時間片(或者沒有可執行的執行緒存在了)為止。

由於在核心中建立或者銷燬執行緒的開銷比較大,所以某些系統會採用可迴圈利用的方式來回收執行緒。當某個執行緒被銷燬時,就把它標誌為不可執行的狀態,但是其內部結構沒有受到影響。稍後,在必須建立一個新執行緒時,就會重新啟用舊執行緒,把它標誌為可用狀態。其實在使用者空間也可以迴圈利用執行緒,但是由於使用者空間建立或者銷燬執行緒開銷小,因此沒有必要。

如果某個程序中的執行緒造成缺頁故障後,核心很容易的就能檢查出來是否有其他可執行的執行緒,如果有的話,在等待所需要的頁面從磁碟讀入時,就選擇一個可執行的執行緒執行。這樣做的缺點是系統呼叫的代價比較大,所以如果執行緒的操作(建立、終止)比較多,就會帶來很大的開銷。

混合實現

結合使用者空間和核心空間的優點,設計人員採用了一種核心級執行緒的方式,然後將使用者級執行緒與某些或者全部核心執行緒多路複用起來

在這種模型中,程式設計人員可以自由控制使用者執行緒和核心執行緒的數量,具有很大的靈活度。採用這種方法,核心只識別核心級執行緒,並對其進行排程。其中一些核心級執行緒會被多個使用者級執行緒多路複用。

總結

這篇文章為你講述作業系統的層面來說,程序和執行緒分別是什麼?程序模型和執行緒模型的區別,程序和執行緒的狀態、層次結構、還有許多的專業術語描述。

下一篇文章我們會把目光放在程序間如何通訊上,也是作業系統級別多執行緒的底層原理,敬請期待。

文章參考:

《現代作業系統》

《Modern Operating System》forth edition

https://www.encyclopedia.com/computing/news-wires-white-papers-and-books/interactive-systems

https://j00ru.vexillium.org/syscalls/nt/32/

https://www.bottomupcs.com/process_hierarchy.xhtml

https://en.wikipedia.org/wiki/Runtime_system

https://en.wikipedia.org/wiki/Execution_model

相關推薦

長文還原程序執行

我們平常說的程序和執行緒更多的是基於程式語言的角度來說的,那麼你真的瞭解什麼是執行緒和程序嗎?那麼我們就從作業系統的角度來了解一下什麼是程序和執行緒。 程序 作業系統中最核心的概念就是 程序,程序是對正在執行中的程式的一個抽象。作業系統的其他所有內容都是圍繞著程序展開的。程序是作業系統提供的最古老也是最重要的

乾貨!兩長文走近神祕的量子糾纏

本文選自《物理》2014年第4、6、9、11期,2015年第1、3期。不管學哪個行業,大概都聽說

長文入門Zookeeper!!!

導讀 文章首發於微信公眾號【碼猿技術專欄】,原創不易,謝謝支援。 Zookeeper 相信大家都聽說過,最典型的使用就是作為服務註冊中心。今天陳某帶大家從零基礎入門 Zookeeper,看了本文,你將會對 Zookeeper 有了初步的瞭解和認識。 注意:本文基於 Zookeeper 的版本是 3.4.14

吐血輸出:2長文細細盤點五種負載均衡策略。

2020 年 5 月 15 日,Dubbo 釋出 2.7.7 release 版本。其中有這麼一個 Features 新增一個負載均衡策略。 熟悉我的老讀者肯定是知道的,Dubbo 的負載均衡我都寫過專門的文章,對每個負載均衡演算法進行了原始碼的解讀,還分享了自己除錯過程中的一些騷操作。 新的負載均衡出來

長文徹底學會攔截器與過濾器

## SpringMVC攔截器介紹 ### 什麼是攔截器 **Spring MVC中的攔截器(Interceptor)類似於Servlet中的過濾器(Filter)**,它主要用於攔截使用者請求並作相應的處理。例如通過攔截器可以進行許可權驗證、記錄請求資訊的日誌、判斷使用者是否登入等。 ### 攔

又長又細,長文解讀Redisson分散式鎖的原始碼

前言 上一篇文章寫了Redis分散式鎖的原理和缺陷,覺得有些不過癮,只是簡單的介紹了下Redisson這個框架,具體的原理什麼的還沒說過呢。趁年前專案忙的差不多了,反正閒著也是閒著,不如把Redisson的原始碼也學習一遍好了。 雖說是一時興起,但仔細研究之後發現Redisson的原始碼解讀工作量還是挺大

一文懟明白程序執行通訊原理

程序間通訊 程序是需要頻繁的和其他程序進行交流的。例如,在一個 shell 管道中,第一個程序的輸出必須傳遞給第二個程序,這樣沿著管道進行下去。因此,程序之間如果需要通訊的話,必須要使用一種良好的資料結構以至於不能被中斷。下面我們會一起討論有關 程序間通訊(Inter Process Communicatio

看完就懂,五千長文領略推薦系統

最近有一些小夥伴給我留言說非常想要我開一個推薦系統專題,其實我也有過這個想法,一直沒動筆主要有兩個原因。第一個原因是擔心自己水平不夠,班門弄斧或者是誤導了一些讀者。第二個原因是,我的確不知道這個專題應該怎麼寫。但是讀者有求,總得迴應不是,所以咬著牙寫了本文。 文章有點長,但是乾貨不少,希望大家能夠耐心讀完。

面試題:1,程序執行的區別?什麼時候用程序?什麼時候用執行?為什麼的專案中用的是執行?為什麼不用程序?如果只有程序,對這個專案有沒有影響?

答:首先得知道什麼是程序什麼是執行緒,我的理解是程序是指在系統中正在執行的一個應用程式;程式一旦執行就是程序,或者更專業化來說:程序是指程式執行時的一個例項,即它是程式已經執行到課中程度的資料結構的彙集。從核心的觀點看,程序的目的就是擔當分配系統資源(CPU時間、記憶體等)的

程序執行——Python中的實現

一、程序(Process)     程序是一個實體。每一個程序都有它自己的地址空間,一般情況下,包括文字區域(text region)、資料區域(data region)和堆疊(stack region)。文字區域儲存處理器執行的程式碼;資料區域儲存變數和程序執行期間使用的動

python 學習第二十二天(程序執行

程序 程序就是一個程式在一個數據集上的一次動態執行過程。 程序一般由程式、資料集、程序控制塊三部分組成。 我們編寫的程式用來描述程序要完成哪些功能以及如何完成; 資料集則是程式在執行過程中所需要使用的資源; 程序控制塊用來記錄程序的外部特徵,描述程序的執行變化過程,系統可以利

CPU的核、程序執行

轉自https://www.cnblogs.com/-new/p/7234332.html   一、CPU與核心 物理核 物理核數量=cpu數(機子上裝的cpu的數量)*每個cpu的核心數 虛擬核 所謂的4核8執行緒,4核指的是物理核心。通過超執行緒技術,用一個物理核模擬

程序執行的區別?什麼時候用程序?什麼時候用執行?----看到好的複製到自己的園子裡哈哈 程序執行的區別?什麼時候用程序?什麼時候用執行

程序和執行緒的區別?什麼時候用程序?什麼時候用執行緒?   答:首先得知道什麼是程序什麼是執行緒? 我的理解是程序是指在系統中正在執行的一個應用程式;程式一旦執行就是程序,或者更專業化來說:程序是指程式執行時的一個例項。 執行緒是程序的一個實體。 程序——資

程序執行及Linux下的程式設計

程序和執行緒及Linux下的程式設計 一、概述 程序和執行緒網路上有一堆解釋,我不喜歡抄襲,也不喜歡套用太教科書的說法。就以我自己的理解來說說程序和執行緒吧,當然自己的理解肯定不是很嚴謹,但是理解起來應該會比教科書快一點。程序和執行緒都可以認為是併發執行程式,但是隻有多處理器下的多執行緒

程序執行概念

1、程序 (1)一次程式的執行; (2)系統進行資源分配和排程的獨立單位; 例如:windows資源管理器裡面的.exe 2、執行緒 (1)程序中獨立執行的子任務 3、區別 (1)程序可以包含多個執行緒; 例如QQ.exe包含資料傳輸執行緒、下載執行緒等 4、多執行緒的

理解作業系統之程序執行

在作業系統中,設定了程序和執行緒的概念去描述程式併發執行邏輯。本文屬於研究程序和執行緒的入門級文章。 主要從以下五個方面介紹程序以及執行緒的相關概念。 程序和執行緒的定義 作業系統中對程序和執行緒的描述 程序的多層排程 程序/執行緒之間的同步機制 程序/執行緒之間的通訊機制 如何避

現代作業系統:程序執行總結

多程序 程序是資源(CPU、記憶體等)分配的基本單位,它是程式執行時的一個例項。程式執行時系統就會建立一個程序,併為它分配資源,然後把該程序放入程序就緒佇列,程序排程器選中它的時候就會為它分配CPU時間,程式開始真正執行。 Linux系統函式fork()可以在父程序中建立一個子程序,

現代作業系統:第二章 程序執行

作業系統中最核心的概念就是程序,這是對正在執行的程式的抽象。 程序是作業系統資源分配的基本單位,而執行緒是任務排程和執行的基本單位。 2.1 程序 作業系統最核心的概念就是程序,它是對正在執行的程式的一個抽象,也可以理解為對處理器的抽象。即使可用的CPU可用,但是依然可以支援多程

linux檢視程序所有子程序執行

linux檢視程序所有子程序和執行緒 原文連線:https://blog.csdn.net/uestczshen/article/details/74091892    問題: 我的程式在其內部建立並執行了多個執行緒,我怎樣才能在該程式建立執行緒後監控其中單個執行緒?

【筆記】程序執行的概念異同

一、程序 1.程序就是一個正在執行的程式。程序是作業系統分配資源的最小單位。 2.程序的缺點: (1)程序佔有系統資源; (2)程序的切換給作業系統帶來了額外的開銷; (3)建立新程序會把父程序的資源複製一份到子程序,如果建立多個程序,會佔用大量資源; (4)程序間的資料共享