12.1 什么是线程

在一个程序中的多个执行路线就叫做线程(thread)。更准确的定义是:线程是一个进程内部的一个控制序列。虽然Linux和许多其他的操作系统一样,都擅长同时运行多个进程,但迄今为止我们看到的所有程序在执行时都是作为一个单独的进程。事实上,所有的进程都至少有一个执行线程。到目前为止,在本书中看到的所有进程都只有一个执行线程。

弄清楚fork系统调用和创建新线程之间的区别非常重要。当进程执行fork调用时,将创建出该进程的一份新副本。这个新进程拥有自己的变量和自己的PID,它的时间调度也是独立的,它的执行(通常)几乎完全独立于父进程。当在进程中创建一个新线程时,新的执行线程将拥有自己的栈(因此也有自己的局部变量),但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。

线程的概念已经出现一段时间了,但在IEEE POSIX委员会发布有关标准之前,它们并没有在类UNIX操作系统中得到广泛支持,而且已存在的线程实现版本也因厂商的不同而有所差异。POSIX 1003.1c规范的发布改变了这一切,线程不仅被很好地标准化了,而且现在绝大多数Linux发行版都已支持它。现在,多核处理器即便对于台式机也已非常普遍,大多数机器在底层硬件上就已物理支持了同时执行多个线程。而此前,对于单核CPU来说,线程的同时执行只是一个聪明、但非常有效的幻觉。

Linux系统在1996年第一次获得线程的支持,我们常把当时使用的函数库称为LinuxThread。LinuxThread已经和POSIX的标准非常接近了(事实上,从许多方面来看,它们之前的区别并不明显),它是在Linux程序设计中迈出的很重要的一步,它使Linux程序员第一次可以在Linux系统中使用线程。但是,在Linux的线程实现版本和POSIX标准之间还是存在着细微的差别,最明显的是关于信号处理部分。这些差别中的大部分都受底层Linux内核的限制,而不是函数库实现所强加的。

许多项目都在研究如何才能改善Linux对线程的支持,这种改善不仅仅是清除POSIX标准和Linux具体实现之间的细微的差别,而且要增强Linux线程的性能和删除一些不需要的限制,其中大部分工作都集中在如何将用户级的线程映射到内核级的线程。在这些项目中有两个主要的项目分别是下一代POSIX线程(New Generation POSIX Thread,简写为NGPT)和本地POSIX线程库(Native POSIX Thread Library,简写为NPTL)。这两个项目都必须修改Linux的内核来支持新的函数库,与旧的Linux线程相比,两者都极大地提升了性能。

2002年,NGPT项目组宣布,由于他们不希望分化线程团队,所以将停止为NGPT添加新功能,而只是继续进行Linux上的线程支持工作,从而有效地将他们的重担放到了NPTL的身上。因此,很明显NPTL将成为Linux线程的新标准。第一个NPTL的主流版本出现在Red Hat Linux版本9上。一些有趣的关于NPTL的背景资料可以参考文章“Linux上的本地POSIX线程库”(The Native POSIX Thread Library for Linux),该文的作者是Ulrich Drepper和Ingo Molnar,在撰写本书时,该文的下载地址为http://people.redhat.com/drepper/nptl-design.pdf。

本章中的大部分代码适用于任何一种线程库,因为这些代码是基于POSIX标准的,而该标准普遍适用于所有的线程库。但是,如果使用的是旧的Linux发行版,你将看到一些细微的区别,特别是用ps命令查看本章示例程序的运行情况时。