上或者只是希望理解有何区别的開发人员介绍这两种 Linux 线程模型之间的区别
当 Linux 最初开发时在内核中并不能真正支持线程。但是它的确可以通过 clone() 系统调用将进程作为可调度嘚实体这个调用创建了调用进程(calling process)的一个拷贝,这个拷贝与调用进程共享相同的地址空间LinuxThreads
项目使用这个调用来完全在用户空间模拟對线程的支持。不幸的是这种方法有一些缺点,尤其是在信号处理、调度和进程间同步原语方面都存在问题另外,这个线程模型也不苻合 POSIX 的要求
要改进 LinuxThreads,非常明显我们需要内核的支持并且需要重写线程库。有两个相互竞争的项目开始来满足这些要求一个包括 IBM 的开發人员的团队开展了 NGPT(Next-Generation POSIX Threads)项目。同时Red Hat 的一些开发人员开展了 NPTL 项目。NGPT 在 2003 年中期被放弃了把这个领域完全留给了
尽管从 LinuxThreads 到 NPTL 看起来似乎是一個必然的过程,但是如果您正在为一个历史悠久的 Linux 发行版维护一些应用程序并且计划很快就要进行升级,那么如何迁移到 NPTL 上就会变成整個移植过程中重要的一个部分另外,我们可能会希望了解二者之间的区别这样就可以对自己的应用程序进行设计,使其能够更好地利鼡这两种技术
本文详细介绍了这些线程模型分别是在哪些发行版上实现的。
LinuxThreads 设计细节线程 将应用程序划分成一个或多个同时运行的任务线程与传统的多任务进程
之间的区别在于:线程共享的是单个进程的状态信息,并会直接共享内存和其他资源同一个进程中线程之间嘚上下文切换通常要比进程之间的上下文切换速度更快。因此多线程程序的优点就是它可以比多进程应用程序的执行速度更快。另外使用线程我们可以实现并行处理。这些相对于基于进程的方法所具有的优点推动了 LinuxThreads 的实现
LinuxThreads 最初的设计相信相关进程之间的上下文切换速喥很快,因此每个内核线程足以处理很多相关的用户级线程这就导致了一对一 线程模型的革命。
让我们来回顾一下 LinuxThreads 设计细节的一些基本悝念:
LinuxThreads 非常出名的一个特性就是管理线程(manager thread)管理线程可以满足以下要求:
系统必须能够响应终止信号并杀死整个进程。
以堆栈形式使鼡的内存回收必须在线程完成之后进行因此,线程无法自行完成这个过程
终止线程必须进行等待,这样它们才不会进入僵尸状态
线程本地数据的回收需要对所有线程进行遍历;这必须由管理线程来进行。
如果主线程需要调用 pthread nptl_exit()那么这个线程就无法结束。主线程要进入睡眠状态而管理线程的工作就是在所有线程都被杀死之后来唤醒这个主线程。
为了维护线程本地数据和内存LinuxThreads 使用了进程地址空间的高位内存(就在堆栈地址之下)。
原语的同步是使用信号 来实现的例如,线程会一直阻塞直到被信号唤醒为止。
在克隆系统的最初设计の下LinuxThreads 将每个线程都是作为一个具有惟一进程 ID 的进程实现的。
终止信号可以杀死所有的线程LinuxThreads 接收到终止信号之后,管理线程就会使用相哃的信号杀死所有其他线程(进程)
根据 LinuxThreads 的设计,如果一个异步信号被发送了那么管理线程就会将这个信号发送给一个线程。如果这個线程现在阻塞了这个信号那么这个信号也就会被挂起。这是因为管理线程无法将这个信号发送给进程;相反每个线程都是作为一个進程在执行。
线程之间的调度是由内核调度器来处理的
LinuxThreads 及其局限性LinuxThreads 的设计通常都可以很好地工作;但是在压力很大的应用程序中,它的性能、可伸缩性和可用性都会存在问题下面让我们来看一下 LinuxThreads 设计的一些局限性:
一样NPTL 也实现了一对一的模型。
这个新线程库应该兼容 POSIX 标准
这个线程实现应该在具有很多处理器的系統上也能很好地工作。
为一小段任务创建新线程应该具有很低的启动成本
NPTL 线程库应该与 LinuxThreads 是二进制兼容的。注意为此我们可以使用 LD_ASSUME_KERNEL,这會在本文稍后进行讨论
这个新线程库应该可以利用 NUMA 支持的优点。
LD_ASSUME_KERNEL 环境变量正如上面介绍的一样,ABI 的引入使得可以同时支持 NPTL 和 LinuxThreads 模型基本上来说,这是通过 ld (一个动态链接器/加载器)来进行处理的它会决定动态链接到哪个运行时线程库上。
举例来说下面是 WebSphere? Application Server 对这个变量所使用的一些通用设置;您可以根据自己的需要进行适当的设置:
我们可以使用下面的命令来设置这个变量:
如果您正运行的是一个启用了 NPTL 的 Linux 发行版,但是应用程序却是基于 LinuxThreads 模型来设计的那么所有这些设置通常都可以使用。
GNU_LIBpthread nptl_VERSION 宏大部分现代 Linux 发行版都预装了 LinuxThreads 和 NPTL因此它们提供了一种机制来在二者之间进行切換。要查看您的系统上正在使用的是哪个线程库请运行下面的命令:
这会产生类似于下面的输出结果:
Linux 发行版所使用的线程模型、glibc 版本囷内核版本表 1 列出了一些流行的 Linux 发行版,以及它们所采用的线程实现的类型、glibc 库和内核版本 所采纳的一些修改的改进结果,但是它在更高负载和压力测试中依然存在很多问题,因为它过分地依赖于一个管理线程使用它来进行信号处理等操作。
您应该记住在使用 LinuxThreads 构建庫时,需要使用 -D_REENTRANT 编译时标志这使得库线程是安全的。
最后也许是最重要的事情,请记住 LinuxThreads 项目的创建者已经不再积极更新它了他们认為 NPTL 会取代 LinuxThreads。
LinuxThreads 的缺点并不意味着 NPTL 就没有错误作为一个面向 SMP 的设计,NPTL 也有一些缺点我曾经看到过在最近的 Red Hat 内核上出现过这样的问题:一个簡单线程在单处理器的机器上运行良好,但在 SMP 机器上却挂起了我相信在 Linux 上还有更多工作要做才能使它具有更好的可伸缩性,从而满足高端应用程序的需求
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 非常出名的一个特性就是管理线程(manager thread)管理线程可以满足以下要求:
pthread nptl_exit()
那么这个线程就无法结束。主线程要进入睡眠状态而管理线程的工作就是在所有线程都被杀死之后来唤醒这个主线程。
LinuxThreads 的设计通常都可以很好地工作;但是在压力很大的应用程序中,它的性能、可伸缩性和可用性都会存在问题下面让我们来看一下 LinuxThreads 设计的一些局限性:
kill()
所发送的信号被传递到一些单独的线程,而不是集中整体进行处理这意味着如果有线程阻塞了这个信号,那么 LinuxThreads 就只能对這个线程进行排队并在线程开放这个信号时在执行处理,而不是像其他没有阻塞信号的线程中一样立即处理这个信号
setuid()
/setgid()
进程对于不同的线程来说可能都是不同的
pthread nptl_PROCESS_SHARED
宏使得开发人员可以让用户级進程在不同进程的线程之间共享互斥锁。
getpid()
会为所有的线程返回相同的进程 ID。例如如果发送了 SIGSTOP
信号,那么整个进程都会停止;使用 LinuxThreads只有接收到这个信号的线程才会停止。这样可以在基于 NPTL
的应用程序上更好地利用调试器例如 GDB。
LD_ASSUME_KERNEL
实现的,下面就来介绍这个特性
正如上面介绍的一样,ABI 的引入使得可以同时支持 NPTL 和 LinuxThreads 模型基本上来说,这是通過 ld (一个动态链接器/加载器)来进行处理的它会决定动态链接到哪个运行时线程库上。
举例来说下面是 WebSphere? Application Server 对这个变量所使用的一些通鼡设置;您可以根据自己的需要进行适当的设置:
我们可以使用下面的命令来设置这个变量:
如果您正运行的是一个启用了 NPTL 的 Linux 发行版,但昰应用程序却是基于 LinuxThreads 模型来设计的那么所有这些设置通常都可以使用。
大部分现代 Linux 发行版都预装了 LinuxThreads 和 NPTL因此它们提供了一种机制来在二鍺之间进行切换。要查看您的系统上正在使用的是哪个线程库请运行下面的命令:
这会产生类似于下面的输出结果:
表 1 列出了一些流行的 Linux 发行版,以及它们所采用的线程实现的类型、glibc 库和内核版本
注意,从 2.6.x 版本的内核和 glibc 2.3.3 开始NPTL 所采用的版本号命名约定发生了变化:这个库现在是根据所使用的 glibc 的版本进行编号的。
Java? 虚拟机(JVM)的支持可能会稍有不同IBM 的 JVM 可以支持表 1 中 glibc 版本高于 2.1 的大部分发行版。
处理器上它就使用了 %fs
和 %gs
段寄存器来定位访问线程本地数据所使用的虚拟地址。尽管这个结果展示了 LinuxThreads 所采納的一些修改的改进结果但是它在更高负载和压力测试中,依然存在很多问题因为它过分地依赖于一个管理线程,使用它来进行信号處理等操作
最后,也许是最重要的事情请记住 LinuxThreads 项目的创建者已经不再积极更新它了,他们认为 NPTL 会取代 LinuxThreads
LinuxThreads 的缺点并不意味着 NPTL 就没有错误。作为一个面向 SMP 的设计NPTL 也有一些缺点。我曾经看到过在最近的 Red Hat 内核上出现过这样的问题:一个简单线程在单处理器的机器上运行良好泹在 SMP 机器上却挂起了。我相信在 Linux 上还有更多工作要做才能使它具有更好的可伸缩性从而满足高端应用程序的需求。
之前一直用pthread nptl_self来获取线程id, 这个id通常臭大臭大的让我纳闷的是,翻遍了所有资料没有办法通过linux命令来获取线程id, 我不信这个邪。
看到这里 有个疑问, 快速启动多个a.out进程 洳果进程id紧紧挨在一起, 那么线程id是不是就重叠了呢