LinuxThreads 項目最初將多線程的概念引入了 Linux®,但是 LinuxThreads 并不遵守 POSIX 線程標準。盡管更新的 Native POSIX Thread Library(NPTL)庫填補了一些空白,但是這仍然存在一些問題。本文為那些需要將自己的應用程序從 LinuxThreads 移植到 NPTL 上或者只是希望理解有何區別的開發人員介紹這兩種 Linux 線程模型之間的區別。
當 Linux 最初開發時,在內核中并不能真正支持線程。但是它的確可以通過 clone()
系統調用將進程作為可調度的實體。這個調用創建了調用進程(calling process)的一個拷貝,這個拷貝與調用進程共享相同的地址空間。LinuxThreads 項目使用這個調用來完全在用戶空間模擬對線程的支持。不幸的是,這種方法有一些缺點,尤其是在信號處理、調度和進程間同步原語方面都存在問題。另外,這個線程模型也不符合 POSIX 的要求。
要改進 LinuxThreads,非常明顯我們需要內核的支持,并且需要重寫線程庫。有兩個相互競爭的項目開始來滿足這些要求。一個包括 IBM 的開發人員的團隊開展了 NGPT(Next-Generation POSIX Threads)項目。同時,Red Hat 的一些開發人員開展了 NPTL 項目。NGPT 在 2003 年中期被放棄了,把這個領域完全留給了 NPTL。
盡管從 LinuxThreads 到 NPTL 看起來似乎是一個必然的過程,但是如果您正在為一個歷史悠久的 Linux 發行版維護一些應用程序,并且計劃很快就要進行升級,那么如何遷移到 NPTL 上就會變成整個移植過程中重要的一個部分。另外,我們可能會希望了解二者之間的區別,這樣就可以對自己的應用程序進行設計,使其能夠更好地利用這兩種技術。
本文詳細介紹了這些線程模型分別是在哪些發行版上實現的。
線程 將應用程序劃分成一個或多個同時運行的任務。線程與傳統的多任務進程 之間的區別在于:線程共享的是單個進程的狀態信息,并會直接共享內存和其他資源。同一個進程中線程之間的上下文切換通常要比進程之間的上下文切換速度更快。因此,多線程程序的優點就是它可以比多進程應用程序的執行速度更快。另外,使用線程我們可以實現并行處理。這些相對于基于進程的方法所具有的優點推動了 LinuxThreads 的實現。
LinuxThreads 最初的設計相信相關進程之間的上下文切換速度很快,因此每個內核線程足以處理很多相關的用戶級線程。這就導致了一對一 線程模型的革命。
讓我們來回顧一下 LinuxThreads 設計細節的一些基本理念:
LinuxThreads 非常出名的一個特性就是管理線程(manager thread)。管理線程可以滿足以下要求:
pthread_exit()
,那么這個線程就無法結束。主線程要進入睡眠狀態,而管理線程的工作就是在所有線程都被殺死之后來喚醒這個主線程。LinuxThreads 的設計通常都可以很好地工作;但是在壓力很大的應用程序中,它的性能、可伸縮性和可用性都會存在問題。下面讓我們來看一下 LinuxThreads 設計的一些局限性:
kill()
所發送的信號被傳遞到一些單獨的線程,而不是集中整體進行處理。這意味著如果有線程阻塞了這個信號,那么 LinuxThreads 就只能對這個線程進行排隊,并在線程開放這個信號時在執行處理,而不是像其他沒有阻塞信號的線程中一樣立即處理這個信號。setuid()
/setgid()
進程對于不同的線程來說可能都是不同的。NPTL,或稱為 Native POSIX Thread Library,是 Linux 線程的一個新實現,它克服了 LinuxThreads 的缺點,同時也符合 POSIX 的需求。與 LinuxThreads 相比,它在性能和穩定性方面都提供了重大的改進。與 LinuxThreads 一樣,NPTL 也實現了一對一的模型。
Ulrich Drepper 和 Ingo Molnar 是 Red Hat 參與 NPTL 設計的兩名員工。他們的總體設計目標如下:
LD_ASSUME_KERNEL
,這會在本文稍后進行討論。與 LinuxThreads 相比,NPTL 具有很多優點:
PTHREAD_PROCESS_SHARED
宏,使得開發人員可以讓用戶級進程在不同進程的線程之間共享互斥鎖。getpid()
會為所有的線程返回相同的進程 ID。例如,如果發送了 SIGSTOP
信號,那么整個進程都會停止;使用 LinuxThreads,只有接收到這個信號的線程才會停止。這樣可以在基于 NPTL 的應用程序上更好地利用調試器,例如 GDB。LD_ASSUME_KERNEL
實現的,下面就來介紹這個特性。正如上面介紹的一樣,ABI 的引入使得可以同時支持 NPTL 和 LinuxThreads 模型?;旧蟻碚f,這是通過 ld (一個動態鏈接器/加載器)來進行處理的,它會決定動態鏈接到哪個運行時線程庫上。
舉例來說,下面是 WebSphere® Application Server 對這個變量所使用的一些通用設置;您可以根據自己的需要進行適當的設置:
LD_ASSUME_KERNEL=2.4.19
:這會覆蓋 NPTL 的實現。這種實現通常都表示使用標準的 LinuxThreads 模型,并啟用浮動堆棧的特性。 LD_ASSUME_KERNEL=2.2.5
:這會覆蓋 NPTL 的實現。這種實現通常都表示使用 LinuxThreads 模型,同時使用固定堆棧大小。我們可以使用下面的命令來設置這個變量:
export LD_ASSUME_KERNEL=2.4.19
注意,對于任何 LD_ASSUME_KERNEL
設置的支持都取決于目前所支持的線程庫的 ABI 版本。例如,如果線程庫并不支持 2.2.5 版本的 ABI,那么用戶就不能將 LD_ASSUME_KERNEL
設置為 2.2.5。通常,NPTL 需要 2.4.20,而 LinuxThreads 則需要 2.4.1。
如果您正運行的是一個啟用了 NPTL 的 Linux 發行版,但是應用程序卻是基于 LinuxThreads 模型來設計的,那么所有這些設置通常都可以使用。
大部分現代 Linux 發行版都預裝了 LinuxThreads 和 NPTL,因此它們提供了一種機制來在二者之間進行切換。要查看您的系統上正在使用的是哪個線程庫,請運行下面的命令:
$ getconf GNU_LIBPTHREAD_VERSION
這會產生類似于下面的輸出結果:
NPTL 0.34
或者:
linuxthreads-0.10
Linux 發行版所使用的線程模型、glibc 版本和內核版本
表 1 列出了一些流行的 Linux 發行版,以及它們所采用的線程實現的類型、glibc 庫和內核版本。
表 1. Linux 發行版及其線程實現
線程實現 C 庫 發行版 內核
LinuxThreads 0.7, 0.71 (for libc5)
libc 5.x
Red Hat 4.2
LinuxThreads 0.7, 0.71 (for glibc 2)
glibc 2.0.x
Red Hat 5.x
LinuxThreads 0.8
glibc 2.1.1
Red Hat 6.0
LinuxThreads 0.8
glibc 2.1.2
Red Hat 6.1 and 6.2
LinuxThreads 0.9
Red Hat 7.2
2.4.7
LinuxThreads 0.9
glibc 2.2.4
Red Hat 2.1 AS
2.4.9
LinuxThreads 0.10
glibc 2.2.93
Red Hat 8.0
2.4.18
NPTL 0.6
glibc 2.3
Red Hat 9.0
2.4.20
NPTL 0.61
glibc 2.3.2
Red Hat 3.0 EL
2.4.21
NPTL 2.3.4
glibc 2.3.4
Red Hat 4.0
2.6.9
LinuxThreads 0.9
glibc 2.2
SUSE Linux Enterprise Server 7.1
2.4.18
LinuxThreads 0.9
glibc 2.2.5
SUSE Linux Enterprise Server 8
2.4.21
LinuxThreads 0.9
glibc 2.2.5
United Linux
2.4.21
NPTL 2.3.5
glibc 2.3.3
SUSE Linux Enterprise Server 9
2.6.5
注意,從 2.6.x 版本的內核和 glibc 2.3.3 開始,NPTL 所采用的版本號命名約定發生了變化:這個庫現在是根據所使用的 glibc 的版本進行編號的。
Java™ 虛擬機(JVM)的支持可能會稍有不同。IBM 的 JVM 可以支持表 1 中 glibc 版本高于 2.1 的大部分發行版。
LinuxThreads 的限制已經在 NPTL 以及 LinuxThreads 后期的一些版本中得到了克服。例如,最新的 LinuxThreads 實現使用了線程注冊來定位線程本地數據;例如在 Intel® 處理器上,它就使用了 %fs
和 %gs
段寄存器來定位訪問線程本地數據所使用的虛擬地址。盡管這個結果展示了 LinuxThreads 所采納的一些修改的改進結果,但是它在更高負載和壓力測試中,依然存在很多問題,因為它過分地依賴于一個管理線程,使用它來進行信號處理等操作。
您應該記住,在使用 LinuxThreads 構建庫時,需要使用 -D_REENTRANT
編譯時標志。這使得庫線程是安全的。
最后,也許是最重要的事情,請記住 LinuxThreads 項目的創建者已經不再積極更新它了,他們認為 NPTL 會取代 LinuxThreads。
LinuxThreads 的缺點并不意味著 NPTL 就沒有錯誤。作為一個面向 SMP 的設計,NPTL 也有一些缺點。我曾經看到過在最近的 Red Hat 內核上出現過這樣的問題:一個簡單線程在單處理器的機器上運行良好,但在 SMP 機器上卻掛起了。我相信在 Linux 上還有更多工作要做才能使它具有更好的可伸縮性,從而滿足高端應用程序的需求。