由于Linux对线程实现的一些特殊性,所以会给我们理解线程及其相关的一些函数带来疑惑,这里做个解答:
1、关于sleep
很多书籍上都说sleep这个系统调用会使整个进程sleep,而不单是sleep一个线程。这样说没错,但我们要是在Linux下写一个多线程 的程序,在一个线程中调用sleep,你会发现其他线程照样运行,这是怎么回事呢?这就是由于Linux下的线程,其实线程的注册、销毁等工作是在 pthread库里面,而在内核中,线程其实就是一个进程。Linux对线程的实现是通过__clone调用实现的,这个调用会new多个进程出来,只不 过的是, Linux会设置这些进程的共享内存空间、文件描述符等属性为相同,这样就实现了线程的定义(共享一个进程的内存空间、线程切换不需要切换上下文环 境......)。所以,很多书籍上会说Linux的线程在核内是轻量级的进程,就是这个道理。因为和普通进程相比,Linux的线程在核内就是一些被设 置了很多属性的进程,从而实现了线程。
所以,在Linux下,一个线程调用了sleep,反映到核内就是sleep了一个轻量级进程,自然其他的线程(核内的轻量级进程)不受影响,照 样可以run了。简言之,之所以sleep一个线程,其他线程仍然可以执行,这是因为在Linux下,线程和进程不是n对1的,而是1对1的。
2、有关线程的调度
有了1的描述,我们就清楚的知道了,其实Linux对线程的调度,策略和进程是基本一致的。所以,很多人问,为什么一个线程中如果有一个循环的 话,其他线程都得不到执行,这就是因为Linux对线程的调度,策略和进程是一样的,即,如果一个线程处于一个高密度的工作状态的话,或者该线程的cpu slice没用完的话,这个线程是不会被调度的。有关这个问题,还可以参看本论坛中“有关线程切换的一个问题”一帖。
其实说白了,明白了第一点的话,很多Linux线程的问题就迎刃而解了。
1、关于sleep
很多书籍上都说sleep这个系统调用会使整个进程sleep,而不单是sleep一个线程。这样说没错,但我们要是在Linux下写一个多线程 的程序,在一个线程中调用sleep,你会发现其他线程照样运行,这是怎么回事呢?这就是由于Linux下的线程,其实线程的注册、销毁等工作是在 pthread库里面,而在内核中,线程其实就是一个进程。Linux对线程的实现是通过__clone调用实现的,这个调用会new多个进程出来,只不 过的是, Linux会设置这些进程的共享内存空间、文件描述符等属性为相同,这样就实现了线程的定义(共享一个进程的内存空间、线程切换不需要切换上下文环 境......)。所以,很多书籍上会说Linux的线程在核内是轻量级的进程,就是这个道理。因为和普通进程相比,Linux的线程在核内就是一些被设 置了很多属性的进程,从而实现了线程。
所以,在Linux下,一个线程调用了sleep,反映到核内就是sleep了一个轻量级进程,自然其他的线程(核内的轻量级进程)不受影响,照 样可以run了。简言之,之所以sleep一个线程,其他线程仍然可以执行,这是因为在Linux下,线程和进程不是n对1的,而是1对1的。
2、有关线程的调度
有了1的描述,我们就清楚的知道了,其实Linux对线程的调度,策略和进程是基本一致的。所以,很多人问,为什么一个线程中如果有一个循环的 话,其他线程都得不到执行,这就是因为Linux对线程的调度,策略和进程是一样的,即,如果一个线程处于一个高密度的工作状态的话,或者该线程的cpu slice没用完的话,这个线程是不会被调度的。有关这个问题,还可以参看本论坛中“有关线程切换的一个问题”一帖。
其实说白了,明白了第一点的话,很多Linux线程的问题就迎刃而解了。
有关Linux下线程的实现方式,再附上一段“POSIX线程编程指南”(来自IBM Developworks)中的描述,这段描述很专业,很到位:
我们知道,Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和 fork (),最终都用不同的参数调用do_fork()核内API。当然,要想实现线程,没有核心对多进程(其实是轻量级进程)共享数据段的支持是不行的,因 此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文 件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统 调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用pthread_create()来创建线程时,则最终设置了所 有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的"进程"拥有共享的运行环境,只有栈是独立的,由 __clone()传入。
Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外 pthread库中进行。pthread库使用一个管理线程(__pthread_manager(),每个进程独立且唯一)来管理线程的创建和终止,为 线程分配线程ID,发送线程相关的信号(比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。
我们知道,Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和 fork (),最终都用不同的参数调用do_fork()核内API。当然,要想实现线程,没有核心对多进程(其实是轻量级进程)共享数据段的支持是不行的,因 此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文 件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统 调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用pthread_create()来创建线程时,则最终设置了所 有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的"进程"拥有共享的运行环境,只有栈是独立的,由 __clone()传入。
Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外 pthread库中进行。pthread库使用一个管理线程(__pthread_manager(),每个进程独立且唯一)来管理线程的创建和终止,为 线程分配线程ID,发送线程相关的信号(比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。