前言:

畢業開始工作三個月,之前一直沒弄明白的程序與執行緒終於有了一個頗為清晰的構想,回想大學時的依樣畫葫蘆,但是始終沒抓住最中心的概念(也許是沒好好聽講也沒好好鑽研?),由此一直對程序與執行緒打心底裡有一種恐懼。然而,程序與執行緒確是工作中不可能避免的內容,愈早愈快理解它們,對聯絡起計算機各個領域還是有極其重要的價值的。也正因此,我把程序和執行緒等的抽象概念實體化,講給我沒有程式設計基礎的朋友們聽,他們表示非常清楚明白,並覺得將這些分享出來可能會對大家有一定的幫助,我這才在此寫下這些。也請大家注意,這只是作為入門的理解用,並非完全嚴謹,它也許能幫助你建立一個框架,而很多細節必定無法完全兼顧,需要讀者自己去鑽研學習。

引子:

程序與執行緒對於曾經的我來說就是兩隻怪獸,他們太過抽象,計算機課經常會拿給你幾個定義,幾段程式碼以及它們執行時候的結果,多半是使用單執行緒和使用多執行緒執行程式時候所花費時間的差異。但是隻透過定義和表現去理解它們如何運作,對於剛入門的我們來說確實不大容易。這篇文章會用類比的方式將抽象的程序與執行緒概念實體化,用這個實體化的類比模擬一個簡單的開發流程,而在文章末尾會回到原點講解教科書上經典的時間耗費的例子。

正文:

讓我們先拋開一切晦澀難懂的術語和詞彙,忘掉執行緒和程序,想象你在玩一個農場大亨的遊戲,構想一個農場,而你是這個農場的管理者。

現在你的農場開張了,大門上的橫幅寫著“Hello World!”,你要開始打理農場了。首先你需要想想農場最開始需要經營什麼專案,好的不管你想要經營什麼,目前我們開始種蘋果。於是在你的農場內你開了一個

蘋果工廠

,負責一切

採購,種植,施肥,收穫

,以及一系列未來可能的專案,例如加工成

蘋果醬,蘋果汁,蘋果罐頭

……

可是整個農場就只有你

一個人

,你就只好自己個兒兼職一切從採購開始做所有的事兒。你幹了大半年,累成了傻子,突然覺得這事兒不行,照這麼下去種蘋果倒還好,要是收穫的季節到了,你一個人收完蘋果,大概蘋果也都爛掉了。於是你看了看人才市場,發現大家都是有超能力的人,所有活都能幹,於是你歡欣鼓舞地領了個

小超人

回家,

和他一起

做這個蘋果工廠的活。你們分工合作,你負責採購施肥,他負責種植收穫,蘋果計劃有條不紊。

忽然間上帝給你託夢,說橙子價格往後會瘋漲,你一想,是時候開一個新的工廠了!於是如同蘋果工廠,你又開了一加

橙子工廠

,當然也和蘋果工廠一致,有一堆活要幹,你便又樂呵地找來另一個小超人打理起你的橙子工廠。往後你又用相同的方式開了

獼猴桃工廠

草莓工廠

之類的……

農場雖說經營面廣,但是久久不見收益,每個工廠就一個小超人實在還是太久,為此你又給每個工廠

加派了三個員工

。當然,

水果生長階段沒法因此提速

,但是

加工效率可就見長了

,收益上了一個新臺階。

此時有使用者反映,咱們農場生產的產品太單一,蘋果就是蘋果產品,草莓就是草莓產品,建議能否加入

什錦果汁,什錦水果罐頭

之類的。那敢情好!於是你開始考慮工廠的職責分配,並且有了以下幾個計劃案:

1。 全都合成一個工廠,命名

水果工廠

,管所有的培育和加工。

2。 建立一個

什錦水果工廠

,專門生產什錦類產品。

3。 把培育職責和加工職責分開,成為

培育工廠

和加工工廠,而加工工廠又分為

果醬工廠,果汁工廠和罐頭工廠

等。

你仔細一想,選擇了第三種,這樣職責分配互相之間比較清晰,而後你也給目前

所有工廠

都分配了四名員工。目前各個水果工廠在收穫完以後,會把相應的水果

送到需要它們的加工工廠

去。並且果汁工廠生產蘋果汁,獼猴桃汁的同時,也生產

橙子蘋果獼猴桃的混合果汁

慢慢地,你發現好些員工在

偷懶不幹活

,你開始調查他們偷懶的原因,發現加工工廠的員工都還是勤勤懇懇,偷懶的全是

培育工廠

的小超人們。你發現他們很多時間要麼在等其他人幹完活,要麼自己的活就是要等待才能完成,這簡直就是人員上的浪費!因此你裁掉了所有培育工廠一半的員工,偷懶的狀況也就好轉不少。

至此,農場終於可以穩定運轉了!

概念:

農場運營到現在,一切都進入了穩步發展的狀態,咱們也就能分析分析主題的概念了,首先給出類比的物件:

裝置

:農場

程序

:工廠(例如此處的蘋果工廠)

執行緒

:工人(例如自己和這位小超人)

任務

:採購,種植,施肥等

這些是咱們農場的主框架,將上面的內容翻譯過來,就是你用你的電腦開始程式設計了,你最開始編了一個程式叫

蘋果工廠

,它會執行一系列任務。

任務與程序/執行緒:

且慢!這些任務是理解程序與執行緒尤為重要的一部分,有必要做一個詳細的探討:

首先我們會有

採購,種植,施肥,收穫

這一系列任務,我們暫且把這一套任務叫

培育

,其次我們可能會有加工成

蘋果醬,蘋果汁,蘋果罐頭

這一些任務,我們管這些叫

加工

現在最需要大家明白的是,

培育

加工

看上去都是一堆任務,其實有著本質的差別:

培育

是順序性的,你不可能先給一個蘋果樹收穫了,再去種下它。你必須按照任務流程來做,先採購好種子,種下去,給它施肥,長出蘋果以後才能收穫。

相反的

加工

是斷序性的,你可以選擇拿蘋果做蘋果醬,也可以選擇做成蘋果汁;你可以先做好蘋果罐頭生意,往後再做蘋果汁的生意;你甚至可以只做蘋果醬而不去加工別的。

在我看來,是這兩種不同的任務模式影響了是否使用多程序或多執行緒

另外需要特別注意的一點是,順序性的任務並不意味著如果任務執行到一半卡住就會斷掉,而是會進行等待,並且持續性地監測是否能夠繼續下去,就好比收穫的過程會在多次施肥後等待果實長成的時候才會進行。

執行緒與多執行緒:

回到正題,你的

蘋果工廠

程式開始運作,這時候就看你電腦快不快了,如果你的電腦是幾十年前的,CPU只有一個核(core),那不好意思你就只是

一個人

在奮鬥,此時

不存在多執行緒的可能性

,不管怎樣你都得自己去完成所有任務。因此你僱了一個小超人,也就是給你的電腦升了個級,你現在CPU有兩個核了,它們就會一起幹活了。

後來你又開了

橙子工廠,獼猴桃工廠和草莓工廠

,這意味著你寫了新的程式,每個工廠程式都執行在各自獨立的

程序

內。而更好的計算機,擁有更多更快CPU核心的計算機,就好比擁有更多更優秀的工人在給你的工廠工作,工作效率也就自然高。

至此

培育階段無法提速

也就對應著順序性的任務,在CPU核心運算速度一致的情況下,即便有幾百幾千的核心在做

培育

的任務,也只能是一項一項進行,因此

多執行緒此時毫無意義

相對應的,如果只有一個在做加工任務,那這個核同一時間只能加工蘋果醬或者蘋果汁,當然它也可以先做一半蘋果醬,再做一半蘋果汁,回頭再把剩餘的蘋果醬和蘋果汁做完,但

整體上所花費的時間是不變的

。可是如果此時有兩個核,你就可以讓一個核做蘋果醬的同時,另一個核加工蘋果汁,整體所花費的時間則縮減了一半,此處就是

多執行緒縮短時間的秘訣

程序與多程序:

使用者提出了什錦類的建議,我們很有必要分析分析幾種不同的計劃案,先大致看看它們在程式設計中,對應程序的狀態都意味著什麼:

1。 全部合為一個

水果工廠

,在程式設計中就好比把所有程式合併成一個整體,這樣就把原本的多個執行在不同程序中的程式,全部執行在了同一個程序

2。 建立

什錦水果工廠

,專職做什錦類產品,在程式設計中相當於開始一個新的程序

3。 重新分配,這在我看來是相比前兩種都要好理解的處理方式,當程式變複雜的時候,把任務細分化單一化,每個程式有自己獨立的職責,互相沒有太強的關聯。每個程式也是執行在自己的程序內。

至此不得不稍微講解一下CPU核心與程序核線程的關係,以清除往後的理解障礙:

CPU的核心只管執行任務,而不關心是在程序內還是執行緒內執行任務,我們可以想象所有的任務都會佔用一個CPU核心,不論這個任務在哪裡,是否屬於順序性或斷序性,重要的是你如何使用這些核心。

因此我們必須要在此確定一個結果,並根據這個結果尋找需要的任務,再看這些任務在完成的時候需要經過哪些階段。目前暫且確定這個結果為

生產一瓶橙子獼猴桃草莓汁

因此對於第一種計劃案,此時顯然只有單程序,我們

從任務來分析

首先各類水果培育由不同的工人在進行,雖然同一種水果培育是順序性的,但是不同水果的培育卻是斷序性的,可以同時進行,因此此處是多執行緒。至此就已經可以確定它為

單程序多執行緒了

第二種計劃案則和原本的狀態相似,每一個工廠都是一個程序,它們執行著相對獨立的任務。同上,因為不同水果的培育是斷序性的,培育橙子,獼猴桃和草莓分別在不同的工廠進行,隨後合併到什錦水果工廠加工為橙子獼猴桃草莓汁。每一個工廠都獨立地進行單一的任務,因此每一個程序都只有一個執行緒在執行,而相對的有多個程序在其中,因此這就是

多執行緒中每個執行緒都只有單程序

第三種計劃案則相對特殊,培育工廠是多執行緒的,可以同時培育各種水果。可是仔細想想,此處的任務線變為了先由培育工廠培育出各類水果,再由加工工廠加工,這是典型的順序性任務,因此雖說此時使用了兩種工廠也就是兩個執行緒,但是加工工廠必須等待培育工廠完成,才可以進行加工處理。因此,

培育工廠是單程序多執行緒

,而整體上看雖說有多個程序,卻並

沒有使用多執行緒時對時間耗費的改進

。(這不意味著把任務細分化無意義)

行程間通訊(Inter-Process Communication – IPC)

之前講解了程序與執行緒之間的區別,而這一章節則會稍微深入瞭解程序與執行緒之間的關係,這也會有助於區別程序與執行緒。

對於之前的三種計劃案,這三種都各有優劣,而談及優劣,則一定會需要了解行程間通訊,也就是IPC。大多數時候,我們預設此類通訊是不需要穿越任何障礙的,從其名稱就可以看出來,如果是行程結束以後大可以用回傳之類的手段傳輸資料,可是在多執行緒和多程序的狀態下,在行程執行的同時,如何調取其他行程的資料呢?(理解此類資料調取的難處非常重要,有待大家自己思考)一般的手段無法解決,行程間通訊就會是穿越可能障礙的通用方式。

在咱們農場的例子中,各個任務都是完成以後交給下一個任務,這種情況下即便有多程序或多執行緒,也不存在行程間通訊。為了實體化行程間通訊,試想農場內我們需要時刻監測溫度和溼度,以保證各個作物的培育和加工都有最佳環境。這個監測過程首先是一定要執行在一個單獨的程序內的,因為我們要保證沒有其他任務會影響監測,更重要的是,這個監測過程不可以中止,還要持續性地獲取各個工廠的資訊,要如何實現呢?

首先我們肯定需要在各工廠安裝溫度和溼度儀,並且把它們連線網路,然後也許定時也許實時地給咱們傳遞資訊,我們就能進行監測了。這也正是行程間通訊需要的,每個程序和執行緒內部我們都設定好埠(socket),將需要監測的內容透過埠進行傳送,從而越過行程自身的執行,而監測溫溼度的行程則也會持續從這個埠調取資訊,從而達到不影響自身行程執行的同時,獲取其他行程的資訊。

到此就需要講到程序與執行緒的一個特點了,程序往往是以一個程式的形式存在的,而執行緒則是程式內部我們建立的。所有大多數時候,執行緒之間因為同屬於一個程序,只要在這個程式內部設定global的變數,那這個變數的內容會被所有此程序內所有執行緒獲取到,從而達到行程間通訊的效果。相對的,程序與程序之間是互相獨立執行的程式,不存在這種方式使其獲得資料,所以才有了使用socket這種通用方式進行資料交流,而達到行程間通訊。