用户线程与内核线程

前言:有两种主要方法实现线程包:在用户空间中和在内核中。这两种方法忽悠利弊,不过混合方式也是可能的。

用户级线程

把整个线程包放在用户空间中,内核对线程包一无所知。从内核角度看,按照单线程进程来管理。

通用结构

线程在一个运行时系统的上层运行,该运行时系统是一个管理线程的过程的集合(这些过程包括pthread_create, pthread_exit, pthread_join和pthread_yield等)。

类似于内核空间的进程表,每个进程都有其专用的线程表,记录线程的程序计数器,堆栈指针,寄存器和状态等。

线程表由运行时系统管理。当一个线程切换到就绪状态或者阻塞状态时,在该线程表中存放重新启动该线程所需的信息。

当某个线程做了一些会引起在本地阻塞的事情之后(例如调用了pthread_join),它调用一个运行时系统的过程,这个过程检查该线程是否必须进入阻塞状态。如果是,它在线程表中保存该线程的寄存器->查看线程表中可运行的就绪线程->把就绪线程的保存至装入寄存器中,只要堆栈指针和程序计数器一被切换,新的线程就自动投入运行。

优点

  1. 线程调度快
    保存线程状态的过程和调度程序都只是本地过程,所以启动它们比进行内核调用效率更高。不需要陷入内核,不需要上下文切换,不需要对内存高速缓存进行刷新,这些都使得线程调度非常快捷

  2. 可定制调度算法
    在某些应用程序中,那些有垃圾收集线程的应用程序就不用担心线程会在不合适的时刻停止

  3. 可拓展性强
    相比内核空间中内核线程的固定表格空间和堆栈空间,用户级线程更能适应大线程数目的情况

  4. 可以为不支持线程的操作系统实现多线程

缺点

  1. 同一进程只能同时有一个线程在运行,一个线程的阻塞会导致进程中所有线程的阻塞
    使用线程的一个目标是,要允许每个线程在调用时可以阻塞(允许阻塞调用),但又要避免被阻塞的线程影响其他线程。显然,这个目标在阻塞系统调用(触发系统调用会阻塞该线程)存在的情况下,是很难实现的。
  1. 如果一个线程开始运行,那么在该进程中的其他线程就不能运行,除非第一个线程自动放弃CPU
    在一个单独的进程内部,没有时钟中断,所以不可能用轮转调度的方式调度线程。除非某个线程能够按照自己的意志进入运行时系统,否则调度程序就没有任何机会。

  2. 缺页中断问题
    如果有一个线程引起页面故障,内核由于甚至不知道有线程存在,通常会把整个进程阻塞知道磁盘I/O完成为止,尽管其他的线程是可以运行。

在上面的讨论中,得知阻塞系统调用是用户级线程的缺点问题所在。下面提出两个可能方法。

  1. 系统调用全部改成非阻塞,但是这需要修改操作系统,一点也不优雅。
  2. 如果某个调用会阻塞,就提前通知。在某些UNIX版本中,有一个系统调用select可以允许调用者通知预期的read是否会阻塞。如果read调用会被阻塞(select调用会告诉你),有关的调用就不进行进行,代之以运行另一个线程。到了下次有关的运行系统取得控制权之后,就可以再次检查看看现在进行read调用是否安全。这个方案需要重写部分系统调用库,所以效率不高也不优雅。

内核级线程

内核支持和管理线程,此时不需要运行时系统,在进程中也没有线程表。相反,在内核中有用来记录系统中所有线程的线程表。当某个线程希望创建一个新线程或撤销一个已有线程时,它进行一个系统调用,这个系统调用通过对线程表的更新完成线程创建或撤销工作。

  1. 在内核中实现线程:当一个线程阻塞时,内核根据其选择,可以运行同一个进程中的另一个线程或者另一个进程中的线程。 在用户空间实现线程:运行时系统始终运行自己进程中的线程,直到内核剥夺它的CPU(或者没有可运行的线程存在了)为止。

混合实现线程

一种方法是使用内核级线程,然后将用户级线程与某些或者全部内核线程多路复用起来。编程人员可以决定有多少个内核级线程和多少个用户级线程彼此多路复用。
内核只识别内核级线程,并对其进行调度。其中一些内核级线程会被多个用户级线程多路复用。如同在没有多线程能力操作系统中某些进程中的用户级线程一样,可以创建撤销调度这些用户级线程。在这种模型中,每个内核级线程有一个可以轮流使用的用户级线程集合。

你可能感兴趣的:(用户线程与内核线程)