閱讀424 返回首頁    go 微軟 go windows


多核時代:並行程序設計探討(3)——Windows和Linux對決(多進程多線程)

並行程序設計探討(3)——WindowsLinux對決(多進程多線程)

前麵的博文經過分析總結,最後得出兩種並行技術:多進程多線程、多機協作。對於多進程和多線程來說,最有代表性且最常見的的莫過於WindowsLinux(作為UNIX類操作係統的代表,下同)這兩個操作係統了。

真是冤家路窄,WindowsLinux這對冤家在這裏又碰麵了!!

當然,我這裏不是要挑起WindowsLinux誰優誰劣的爭論,對於一個真正的技術人來說,WindowsLinux本身並沒有優劣之分,隻有在不同的使用場景下用誰會更好的問題。之所以將WindowsLinux拿來對比,是因為對比更加容易讓人理解,記憶也更加深刻!

下麵我們首先從多進程和多線程的實現機製方麵來對比WindowsLinux

                                         多進程多線程實現機製

說起進程和線程,估計大家都會立刻想起那句耳熟能詳的解釋“進程是資源分配的最小單位,線程是運行的最小單位”!。

理論上來說這是對的,但實際上來說就不一定了,例如Windows有進程和線程的概念,而傳統UNIX卻隻有進程的概念(例如經典的《UNIX環境高級編程》中就沒有多線程的概念,但SolarisAIX等又有另外的實現,此處暫且不表),Linux也有進程和線程的概念,但實現機製和Windows又不一樣,真是林子大了什麼鳥都有:)

有幾個進程、線程相關的概念首先要簡單介紹一下:

1.1   概念介紹

1.1.1  進程

資源分配最小單位,有的操作係統還是運行最小單位;

1.1.2   線程

運行最小單位,也是CPU調度的最小單位;

1.1.3   ULTKLT

用戶態線程和內核態線程;主要的區分就是“誰來管理”線程,用戶態是用戶管理,內核態是內核管理(但肯定要提供一些API,例如創建)。

簡單對比兩者優劣勢:

1)可移植性:因為ULT完全在用戶態實現線程,因此也就和具體的內核沒有什麼關係,可移植性方麵ULT略勝一籌;

2)可擴展性:ULT是由用戶控製的,因此擴展也就容易;相反,KLT擴展就很不容易,基本上隻能受製於具體的操作係統內核;

3)性能:由於ULT的線程是在用戶態,對應的內核部分還是一個進程,因此ULT就沒有辦法利用多處理器的優勢,而KLT就可以通過調度將線程分布在多個處理上運行,這樣KLT的性能高得多;另外,一個ULT的線程阻塞,所有的線程都阻塞,而KLT一個線程阻塞不會影響其它線程。

4)編程複雜度:ULT的所有管理工作都要由用戶來完成,而KLT僅僅需要調用API接口,因此ULT要比KLT複雜的多;

1.1.4   POSIX

為了解決不同操作係統之間移植時接口不兼容而製定的接口標準,詳見維基百科解釋:https://zh.wikipedia.org/wiki/POSIX

1.1.5  NPTL

為了解決Linux原有線程實現機製的缺陷而創立的一個開源項目,從2.4開始就有發布版本采用NPTL來實現多線程支持了。詳見維基百科解釋https://zh.wikipedia.org/wiki/Native_POSIX_Thread_Library

1.1.6   LWP

Lightweight Process,輕量級進程,看名字有點奇怪,為什麼叫輕量級進程呢?為什麼又要用輕量級線程呢?

看了前麵ULTKLT的比較,估計大家也發現了一個問題:所謂的ULT,因為不能利用多處理器的優勢和線程互相阻塞,其實完全不能堪重任,但對於傳統UNIXLinux這類操作係統,內核設計和實現的時候就沒有線程這種對象,那怎麼實現多線程呢?

天才們於是想出了LWP這個招數,說白了這就是一個“山寨版的進程”,完全具有了山寨的一切特征:

文件係統是原來的進程的;

文件描述符是原來的進程的;

信號處理是原來的進程的;

地址空間是原來的進程的;

但就是進程ID不是原來的進程的,你說像不像BlackBerry的山寨版BlockBerry

 

詳情請參考維基百科解釋:https://en.wikipedia.org/wiki/Light-weight_process

1.2  詳細對比

1.2.1  Windows

在此要向Windows致敬:至少相比Linux來說,Windows在線程上的支持是Linux不能比的(不要跟我提DOS哈)!

Windows的實現機製簡單來說就是前麵提到的KLT,即Windows在內核級別支持線程。每個Windows進程至少有一個線程,係統調度的時候也是調度線程。

當創建一個進程時,係統會自動創建它的第一個線程,稱為主線程。然後,該線程可以創建其他的線程,而這些線程又能創建更多的線程。

Windows已經提供了線程編程係列的API,這裏就不詳述了。

1.2.2  Linux

Linux不同的版本有不同的實現,2.0~2.4實現的是俗稱LinuxThreads的多線程方式,到了2.6,基本上都是NPTL的方式了。下麵我們分別介紹。

1.2.2.1 LinuxThreads      

注:以下內容主要參考“楊沙洲 (mailto:pubb@163.net?subject=Linux 線程實現機製分析&cc=pubb@163.net)國防科技大學計算機學院”的“Linux 線程實現機製分析”。

這種實現本質上是一種LWP的實現方式,即通過輕量級進程來模擬線程,內核並不知道有線程這個概念,在內核看來,都是進程。

Linux采用的“一對一”的線程模型,即一個LWP對應一個線程。這個模型最大的好處是線程調度由內核完成了,而其他線程操作(同步、取消)等都是核外的線程庫函數完成的。

LinuxThreads中,專門為每一個進程構造了一個管理線程,負責處理線程相關的管理工作。當進程第一次調用pthread_create()創建一個線程的時候就會創建並啟動管理線程。然後管理線程再來創建用戶請求的線程。也就是說,用戶在調用pthread_create後,先是創建了管理線程,再由管理線程創建了用戶的線程。

這種通過LWP的方式來模擬線程的實現看起來還是比較巧妙的,但也存在一些比較嚴重的問題:

1)線程ID和進程ID的問題

按照POSIX的定義,同一進程的所有的線程應該共享同一個進程和父進程ID,而Linux的這種LWP方式顯然不能滿足這一點。

2)信號處理問題

異步信號是以進程為單位分發的,而Linux的線程本質上每個都是一個進程,且沒有進程組的概念,所以某些缺省信號難以做到對所有線程有效,例如SIGSTOPSIGCONT,就無法將整個進程掛起,而隻能將某個線程掛起。

3)線程總數問題

LinuxThreads將每個進程的線程最大數目定義為1024,但實際上這個數值還受到整個係統的總進程數限製,這又是由於線程其實是核心進程。

4)管理線程問題

管理線程容易成為瓶頸,這是這種結構的通病;同時,管理線程又負責用戶線程的清理工作,因此,盡管管理線程已經屏蔽了大部分的信號,但一旦管理線程死亡,用戶線程就不得不手工清理了,而且用戶線程並不知道管理線程的狀態,之後的線程創建等請求將無人處理。

5)同步問題

LinuxThreads中的線程同步很大程度上是建立在信號基礎上的,這種通過內核複雜的信號處理機製的同步方式,效率一直是個問題。

6)其他POSIX兼容性問題

Linux中很多係統調用,按照語義都是與進程相關的,比如nicesetuidsetrlimit等,在目前的LinuxThreads中,這些調用都僅僅影響調用者線程。

7)實時性問題

線程的引入有一定的實時性考慮,但LinuxThreads暫時不支持,比如調度選項,目前還沒有實現。不僅LinuxThreads如此,標準的Linux在實時性上考慮都很少。

1.2.2.2   NPTL的實現

NPTLNative POSIX Thread Library,天生的POSIX線程庫。從命名上也可以看出所謂的NPTL就是針對原來的LinuxThreads的,不然為啥叫“Native”呢:)

本質上來說,NPTL還是一個LWP的實現機製,但相對原有LinuxThreads來說,做了很多的改進。下麵我們看一下NPTL如何解決原有LinuxThreads實現機製的缺陷。

1)線程ID和進程ID問題

新的exec函數能夠創建和原有進程ID一樣ID的新進程,這樣所有的線程ID都是一樣的;且/Proc目錄下隻會顯示進程的初始線程(初始線程就代表整個進程,類似於Windows的進程中第一個線程s),不會再像以前LinuxThreads機製時每個線程在proc目錄下都有記錄。

2)信號處理問題

內核實現了POSIX要求的線程信號處理機製,發送給進程的信號將由內核分發給一個合適的線程處理,對於致命和全局的信號(例如StopContinue Pending,所有的線程都同步處理。

3)線程總數問題

內核經過擴展,能夠處理任意數量的線程。PID空間經過擴展後,在IA-32係統上能夠最大支持20億線程。

4)管理線程問題

去掉管理進程,管理進程的任務由擴展後的clone函數完成;增加了exit_group的係統調用,用於退出整個進程;

5)信號同步問題

實現了一個叫做FutexFase Userspace Mutex,注意不是Mutex)機製用來完成線程間同步,Futex的主要操作是在用戶態完成的,這樣解決了依靠內核信號機製進行同步的效率問題。詳細請參考https://zh.wikipedia.org/wiki/Futex

 

當然,NPTL雖然做了很多改進,但依然不是100% POSIX兼容的,LinuxThreads的第6個和第7個問題在NPTL機製下依然沒有解決,但這並不掩蓋NPTL帶來的巨大改進,下麵是性能對比圖:

NPTL官方的文檔:https://people.redhat.com/drepper/nptl-design.pdf

1.3    對決?

看了前麵的分析,大家可能納悶了,這哪裏是對決哦?全部是講Linux的實現了。

其實我也鬱悶,本來應該更加詳細的介紹Windows實現機製的,但由於Linux的不爭氣,全部用來變成對它的分析了。

 

 

==========================未完待續===============================

最後更新:2017-04-02 03:42:36

  上一篇:go [原創]和Taskmgr過不去篇(無厘頭版)
  下一篇:go Blowfish加密算法