萬盛學電腦網

 萬盛學電腦網 >> Linux教程 >> Linux線程比較:LinuxThreads 和NPTL

Linux線程比較:LinuxThreads 和NPTL

class="152507">

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 設計細節的一些基本理念:

  • LinuxThreads 非常出名的一個特性就是管理線程(manager thread)。管理線程可以滿足以下要求:

    • 系統必須能夠響應終止信號並殺死整個進程。
    • 以堆棧形式使用的內存回收必須在線程完成之後進行。因此,線程無法自行完成這個過程。
    • 終止線程必須進行等待,這樣它們才不會進入僵屍狀態。
    • 線程本地數據的回收需要對所有線程進行遍歷;這必須由管理線程來進行。
    • 如果主線程需要調用 pthread_exit(),那麼這個線程就無法結束。主線程要進入睡眠狀態,而管理線程的工作就是在所有線程都被殺死之後來喚醒這個主線程。

    ;
  • 為了維護線程本地數據和內存,LinuxThreads 使用了進程地址空間的高位內存(就在堆棧地址之下)。

    ;
  • 原語的同步是使用信號 來實現的。例如,線程會一直阻塞,直到被信號喚醒為止。

    ;
  • 在克隆系統的最初設計之下,LinuxThreads 將每個線程都是作為一個具有惟一進程 ID 的進程實現的。

    ;
  • 終止信號可以殺死所有的線程。LinuxThreads 接收到終止信號之後,管理線程就會使用相同的信號殺死所有其他線程(進程)。

    ;
  • 根據 LinuxThreads 的設計,如果一個異步信號被發送了,那麼管理線程就會將這個信號發送給一個線程。如果這個線程現在阻塞了這個信號,那麼這個信號也就會被掛起。這是因為管理線程無法將這個信號發送給進程;相反,每個線程都是作為一個進程在執行。

    ;
  • 線程之間的調度是由內核調度器來處理的。

LinuxThreads 及其局限性

LinuxThreads 的設計通常都可以很好地工作;但是在壓力很大的應用程序中,它的性能、可伸縮性和可用性都會存在問題。下面讓我們來看一下 LinuxThreads 設計的一些局限性:

  • 它使用管理線程來創建線程,並對每個進程所擁有的所有線程進行協調。這增加了創建和銷毀線程所需要的開銷。

    ;
  • 由於它是圍繞一個管理線程來設計的,因此會導致很多的上下文切換的開銷,這可能會妨礙系統的可伸縮性和性能。

    ;
  • 由於管理線程只能在一個 CPU 上運行,因此所執行的同步操作在 SMP 或 NUMA 系統上可能會產生可伸縮性的問題。

    ;
  • 由於線程的管理方式,以及每個線程都使用了一個不同的進程 ID,因此 LinuxThreads 與其他與 POSIX 相關的線程庫並不兼容。

    ;
  • 信號用來實現同步原語,這會影響操作的響應時間。另外,將信號發送到主進程的概念也並不存在。因此,這並不遵守 POSIX 中處理信號的方法。

    ;
  • LinuxThreads 中對信號的處理是按照每線程的原則建立的,而不是按照每進程的原則建立的,這是因為每個線程都有一個獨立的進程 ID。由於信號被發送給了一個專用的線程,因此信號是串行化的 ―― 也就是說,信號是透過這個線程再傳遞給其他線程的。這與 POSIX 標准對線程進行並行處理的要求形成了鮮明的對比。例如,在 LinuxThreads 中,通過 kill() 所發送的信號被傳遞到一些單獨的線程,而不是集中整體進行處理。這意味著如果有線程阻塞了這個信號,那麼 LinuxThreads 就只能對這個線程進行排隊,並在線程開放這個信號時在執行處理,而不是像其他沒有阻塞信號的線程中一樣立即處理這個信號。

    ;
  • 由於 LinuxThreads 中的每個線程都是一個進程,因此用戶和組 ID 的信息可能對單個進程中的所有線程來說都不是通用的。例如,一個多線程的 setuid()/setgid() 進程對於不同的線程來說可能都是不同的。

    ;
  • 有一些情況下,所創建的多線程核心轉儲中並沒有包含所有的線程信息。同樣,這種行為也是每個線程都是一個進程這個事實所導致的結果。如果任何線程發生了問題,我們在系統的核心文件中只能看到這個線程的信息。不過,這種行為主要適用於早期版本的 LinuxThreads 實現。

    ;
  • 由於每個線程都是一個單獨的進程,因此 /proc 目錄中會充滿眾多的進程項,而這實際上應該是線程。

    ;
  • 由於每個線程都是一個進程,因此對每個應用程序只能創建有限數目的線程。例如,在 IA32 系統上,可用進程總數 ―― 也就是可以創建的線程總數 ―― 是 4,090。

    ;
  • 由於計算線程本地數據的方法是基於堆棧地址的位置的,因此對於這些數據的訪問速度都很慢。另外一個缺點是用戶無法可信地指定堆棧的大小,因為用戶可能會意外地將堆棧地址映射到本來要為其他目的所使用的區域上了。按需增長(grow on demand) 的概念(也稱為浮動堆棧 的概念)是在 2.4.10 版本的 Linux 內核中實現的。在此之前,LinuxThreads 使用的是固定堆棧。

關於 NPTL

NPTL,或稱為 Native POSIX Thread Library,是 Linux 線程的一個新實現,它克服了 LinuxThreads 的缺點,同時也符合 POSIX 的需求。與 LinuxThreads 相比,它在性能和穩定性方面都提供了重大的改進。與 LinuxThreads 一樣,NPTL 也實現了一對一的模型。

Ulrich Drepper 和 Ingo Molnar 是 Red Hat 參與 NPTL 設計的兩名員工。他們的總體設計目標如下:

  • 這個新線程庫應該兼容 POSIX 標准。

    ;
  • 這個線程實現應該在具有很多處理器的系統上也能很好地工作。

    ;
  • 為一小段任務創建新線程應該具有很低的啟動成本。

    ;
  • NPTL 線程庫應該與 LinuxThreads 是二進制兼容的。注意,為此我們可以使用 LD_ASSUME_KERNEL,這會在本文稍後進行討論。

    ;
  • 這個新線程庫應該可以利用 NUMA 支持的優點。

NPTL 的優點

與 LinuxThreads 相比,NPTL 具有很多優點:

  • NPTL 沒有使用管理線程。管理線程的一些需求,例如向作為進程一部分的所有線程發送終止信號,是並不需要的;因為內核本身就可以實現這些功能。內核還會處理每個線程堆棧所使用的內存的回收工作。它甚至還通過在清除父線程之前進行等待,從而實現對所有線程結束的管理,這樣可以避免僵屍進程的問題。

    ;
  • 由於 NPTL 沒有使用管理線程,因此其線程模型在 NUMA 和 SMP 系統上具有更好的可伸縮性和同步機制。

    ;
  • 使用 NPTL 線程庫與新內核實現,就可以避免使用信號來對線程進行同步了。為了這個目的,NPTL 引入了一種名為 futex 的新機制。futex 在共享內存區域上進行工作,因此可以在進程之間進行共享,這樣就可以提供進程間 POSIX
copyright © 萬盛學電腦網 all rights reserved