Linux posix线程库总结

   因为历史缘由,2.5.x之前的linux对pthreads没有提供内核级的支持,因此在linux上的pthreads实现只能采用n:1的方式,也称为库实现。html

线程的实现,经历了以下发展阶段:linux

  • LinuxThreads : Linux2.6以前
  • NPTL (Native Posix Thread Library) : RedHat负责,Linux2.6以后
  • NGPT (Next Generation Posix Thread): IBM负责,同NPTL同时开始研究的,可是最后被抛弃了(IBM......)

目前,pthreads的实现有3种方式:算法

(1)第一种,多对一,linux Threads,也就是库实现。shell

  linux Threads,这是linux标准的的线程库,可是与IEEE的POSIX不兼容. 在LinuxThreads中,专门为每个进程构造了一个管理线程,负责处理线程相关的管理工做。当进程第一次调用pthread_create()建立一个线程的时候就会建立并启动管理线程。而后管理线程再来建立用户请求的线程。也就是说,用户在调用pthread_create后,先是建立了管理线程,再由管理线程建立了用户的线程。编程

(2)第二种,1:1模式。 NPTL多线程

 

  1:1模式适合CPU密集型的机器。咱们知道,进程(线程)在运行中会因为等待某种资源而阻塞,多是I/O资源,也多是CPU。在多CPU机器上1:1模式是个很不错的选择。所以1:1的优势就是,可以充分发挥SMP的优点。
这种模式也有它的缺点。因为OS为每一个线程创建一个内核线程,致使内核级的内存空间(IA32机器的4G虚存空间的最高1G)的大开销。第二个缺点在于,在传统意义上,在mutex互斥锁和条件变量上的操做要求进入内核态,这是由于OS负责调度,它要为线程的转态转换负全责。后面咱们会看到,Linux的NPTL库避免了这个缺点,它的互斥锁与条件变量的操做在用户态完成。post

(3)第三种,M:N模式。测试

  这种模式试图兼有上面2种模式的优势。它要求一个分层的模型。比方说,某个进程内有4个线程,其中每2个线程对应一个内核线程。这样,OS知道2个实体的存在,负责它们的调度。而在一个实体内有2个线程,这2个线程间的调度就是OS不加干涉、也不知道的了。
简单的说,M:N模型的OS级调度上,跟1:1模型类似;线程间调度上,跟n:1模型类似。
优势是,既能够在进程内利用SMP的优点,又能够节省系统空间内存的消耗,并且环境切换大多在用户态完成。
缺点是显然的:复杂。
spa

 

  Linux线程是经过进程来实现。Linux kernel为进程建立提供一个clone()系统调用,clone的参数包括如 CLONE_VM, CLONE_FILES, CLONE_SIGHAND 等。经过clone()的参数,新建立的进程,也称为LWP(Lightweight process)与父进程共享内存空间,文件句柄,信号处理等,从而达到建立线程相同的目的。操作系统

 

普通进程和LWP在实现上的不一样点是:

  • 普通进程,fork会调用clone,第三个参数flags不会包含CLONE_THREAD
  • LWP,pthread_create会调用clone,第三个参数flags会包含CLONE_THREAD(可能还有其余几个标志),标示子进程和父进程同属一个线程组(相同TGID)

线程和LWP是同一个东西,只是在用户态,咱们管进程中每个执行序列为“线程”,可是内核中它被称为LWP。由于内核上没有线程的概念,CPU的调度是以进程为单位的。

 

  在Linux 2.6以前,Linux kernel并无真正的thread支持,一些thread library都是在clone()基础上的一些基于user space的封装,所以一般在信号处理、进程调度(每一个进程须要一个额外的调度线程)及多线程之间同步共享资源等方面存在必定问题。Linux 2.6的线程库叫NPTL(Native POSIX Thread Library)。POSIX thread(pthread)是一个编程规范,经过此规范开发的多线程程序具备良好的跨平台特性。尽管是基于进程的实现,但新版的NPTL建立线程的效率很是高。一些测试显示,基于NPTL的内核建立10万个线程只须要2秒,而没有NPTL支持的内核则须要长达15分钟。

  在Linux中,每个线程都有一个task_struct。线程和进程可使用同一调度其调度。内核角度上来将LWP和Process没有区别,有的仅仅是资源的共享。若是独享资源则是HWP,共享资源则是LWP。而在真正内核实现的NPTL的实现是在kernel增长了futex(fast userspace mutex)支持用于处理线程之间的sleep与wake。futex是一种高效的对共享资源互斥访问的算法。kernel在里面起仲裁做用,但一般都由进程自行完成。NPTL是一个1×1的线程模型,即一个线程对于一个操做系统的调度进程,优势是很是简单。而其余一些操做系统好比Solaris则是MxN的,M对应建立的线程数,N对应操做系统能够运行的实体。(N<M),优势是线程切换快,但实现稍复杂。

 

注:

(1)pthread线程库--NPTL(Native POSIX Threading Library)

  在1:1核心线程模型中,应用程序建立的每个线程(也有书称为LWP)都由一个核心线程直接管理。OS内核将每个核心线程都调到系统CPU上,

所以,全部线程都工做在“系统竞争范围”(system contention scope):线程直接和“系统范围”内的其余线程竞争。

(2)NGPT(Next Generation POSIX Threads)

  N:M混合线程模型提供了两级控制,将用户线程映射为系统的可调度体以实现并行,这个可调度体称为轻量级进程(LWP:light weight process),LWP

再一一映射到核心线程。以下图所示。OS内核将每个核心线程都调到系统CPU上,所以,全部线程都工做在“系统竞争范围”。

 

添加:各阶段线程的具体实现

LinuxThreads

在LinuxThreads中,专门为每个进程构造了一个管理线程,负责处理线程相关的管理工做。当进程第一次调用pthread_create()建立一个线程的时候就会建立并启动管理线程。而后管理线程再来建立用户请求的线程。也就是说,用户在调用pthread_create后,先是建立了管理线程,再由管理线程建立了用户的线程。

为了遵循POSIX对线程的一个规定:当"进程"收到一个致命信号(好比因为段错误收到SIGSEGV信号), 进程内的线程所有退出,LinuxThreads的实现方法:

  1. 程序第一次调用pthread_create时, linuxthreads发现管理线程不存在, 因而建立这个管理线程. 这个管理线程是进程中的第一个线程(主线程)的儿子.
  2. 而后在pthread_create中, 会经过pipe向管理线程发送一个命令, 告诉它建立线程. 便是说, 除主线程外, 全部的线程都是由管理线程来建立的, 管理线程是它们的父亲.
  3. 因而, 当任何一个子线程退出时, 管理线程将收到SIGUSER1信号(这是在经过clone建立子线程时指定的). 管理线程在对应的sig_handler中会判断子线程是否正常退出, 若是不是, 则杀死全部线程, 而后自杀.
  4. 主线程是管理线程的父亲, 其退出时并不会给管理线程发信号. 因而, 在管理线程的主循环中经过getppid检查父进程的ID号, 若是ID号是1, 说明父亲已经退出, 并把本身托管给了init进程(1号进程). 这时候, 管理线程也会杀掉全部子线程, 而后自杀.

容易发现,管理线程可能成为多线程系统的瓶颈,线程建立和销毁的开销很大(须要IPC)。
更为重要的是,LinuxThreads没法知足Posix对线程的绝大多数规定,好比:

  • 查看进程列表的时候, 相关的一组task_struct应当被展示为列表中的一个节点;
  • 发送给这个"进程"的信号(对应kill系统调用), 将被对应的这一组task_struct所共享, 而且被其中的任意一个"线程"处理;
  • 发送给某个"线程"的信号(对应pthread_kill), 将只被对应的一个task_struct接收, 而且由它本身来处理;
  • 当"进程"被中止或继续时(对应SIGSTOP/SIGCONT信号), 对应的这一组task_struct状态将改变;

NPTL

在linux 2.6中, 内核有了线程组的概念, task_struct结构中增长了一个tgid(thread group id)字段.
若是这个task是一个"主线程", 则它的tgid等于pid, 不然tgid等于进程的pid(即主线程的pid).

经过以下方式,解决了LinuxThreads不能兼容POSIX的问题:

  • 有了tgid, 内核或相关的shell程序就知道某个tast_struct是表明一个进程仍是表明一个线程, 也就知道在何时该展示它们, 何时不应展示(好比在ps的时候, 线程就不要展示了).
  • 为了应付"发送给进程的信号"和"发送给线程的信号", task_struct里面维护了两套signal_pending, 一套是线程组共享的, 一套是线程独有的。经过kill发送的信号被放在线程组共享的signal_pending中, 能够由任意一个线程来处理; 经过pthread_kill发送的信号(pthread_kill是pthread库的接口, 对应的系统调用中tkill)被放在线程独有的signal_pending中, 只能由本线程来处理.
  • 当线程中止/继续, 或者是收到一个致命信号时, 内核会将处理动做施加到整个线程组中.

NGPT

上面提到的两种线程库使用的都是内核级线程(每一个线程都对应内核中的一个调度实体), 这种模型称为1:1模型(1个线程对应1个内核级线程);
而NGPT则打算实现M:N模型(M个线程对应N个内核级线程), 也就是说若干个线程多是在同一个执行实体上实现的.

由于模型太复杂,貌似没有实现出来全部预约功能,因此被放弃了。

 

参考连接

linux中的线程的实质和实现  http://particle128.com/posts/2014/05/linux-thread.html

Linux进程、线程模型,LWP,pthread_self() http://blog.csdn.net/tianyue168/article/details/7403693

相关文章
相关标签/搜索