linux线程

许多教程中,总是把进程定义为程序的执行实例,它并不执行什么,只是维护应用程序所需的各种资源。

而线程则是真正的执行实体,为了让进程完成一定的工作,进程必须至少包含一个线程。

进程所维护的是程序所包含的资源(静态资源),如:地址空间,打开的文件句柄集,文件系统状态,信号处理handler等。

线程所维护的是运行相关的资源(动态资源),如:运行栈,调度相关的控制信息,待处理的信号集等。

然而,一直以来,linux内核并没有线程的概念。每一个执行实体都是一个task_struct结构,通常称之为进程。

linux进程是一个执行单元,维护着执行相关的动态资源,同时,它又引用着程序所需的静态资源。

通过系统调用clone创建子进程时,可以有选择性地让子进程共享父进程所引用的资源,这样的子进程通常称为轻量级进程。

linux上的线程就是基于轻量级进程,由用户态的pthread库实现的。

使用pthread以后,在用户看来,每一个task_struct就对应一个线程,而一组线程以及它们所共同引用的一组资源就是一个进程。

但是,一组线程并不仅仅是引用同一组资源就够了,它们还必须被视为一个整体,对此,POSIX标准提出了如下要求:

1、查看进程列表的时候,相关的一组task_struct应当被展现为列表中的一个节点

2、发送给这个“进程”的信号(对应kill系统调用),将被对应的这一组task_struct所共享,并且被其中的任意一个“线程”处理

3、发送给某个“线程”的信号(对应pthread_kill),将只被对应的一个task_struct接收,并且由它自己来处理

4、当“进程”被停止或继续时(对应SIGSTOP/SIGCONT信号),对应的这一组task_struct状态将改变

5、当“进程”收到一个致命信号(比如由于段错误收到SIGSEGV信号),对应的这一组task_struct将全部退出

6、等等(以上可能不够全)

linuxthreads

在linux 2.6以前,pthread线程库对应的实现是一个名叫linuxthreads的库。

linuxthreads利用前面提到的轻量级进程来实现线程,但是对于POSIX提出的那些要求,linuxthreads除了第5点以外,都没有实现(实际上是无能为力):

1、如果运行了A程序,A程序创建了10个线程,那么在shell下执行ps命令时将看到11个A进程,而不是1个(注意,也不是10个,下面会解释)

2、不管是kill还是pthread_kill, 信号只能被一个对应的线程所接收

3、SIGSTOP/SIGCONT信号只对一个线程起作用

还好linuxthreads实现了第5点,我认为这一点是最重要的。

如果某个线程“挂”了,整个进程还在若无其事地运行着,可能会出现很多的不一致状态,进程将不是一个整体,而线程也不能称为线程了。

或许这也是为什么linuxthreads虽然与POSIX的要求差距甚远,却能够存在,并且还被使用了好几年的原因吧。

但是,linuxthreads为了实现这个“第5点”,还是付出了很多代价,并且创造了linuxthreads本身的一大性能瓶颈。

接下来要说说为什么A程序创建了10个线程,但是ps时却会出现11个A进程了。

因为linuxthreads自动创建了一个管理线程,上面提到的“第5点”就是靠管理线程来实现的。

当程序开始运行时,并没有管理线程存在(因为尽管程序已经链接了pthread库,但是未必会使用多线程)。

程序第一次调用pthread_create时,linuxthreads发现管理线程不存在,于是创建这个管理线程,这个管理线程是进程中的第一个线程(主线程)的儿子。

然后在pthread_create中,会通过pipe向管理线程发送一个命令,告诉它创建线程,即是说,除主线程外,所有的线程都是由管理线程来创建的,管理线程是它们的父亲。

于是,当任何一个子线程退出时,管理线程将收到SIGUSER1信号(这是在通过clone创建子线程时指定的)。

管理线程在对应的sig_handler中会判断子线程是否正常退出,如果不是,则杀死所有线程,然后自杀。

那么,主线程怎么办呢?主线程是管理线程的父亲,其退出时并不会给管理线程发信号,于是,在管理线程的主循环中通过getppid检查父进程的ID号,如果ID号是1,说明父亲已经退出,并把自己托管给了init进程(1号进程),这时候,管理线程也会杀掉所有子线程,然后自杀。

可见, 线程的创建与销毁都是通过管理线程来完成的,于是管理线程就成了linuxthreads的一个性能瓶颈,

创建与销毁需要一次进程间通信,一次上下文切换之后才能被管理线程执行,并且多个请求会被管理线程串行地执行。

相关推荐