线程的实现

这篇博客探讨了线程的三种实现方式:POSIX线程、用户空间线程和内核空间线程。用户空间线程实现允许在不支持线程的操作系统上使用,但面临阻塞系统调用的问题。内核空间线程则通过系统调用管理线程,提供了更好的阻塞处理,但系统调用开销较大。混合实现尝试结合两者的优点,提供更灵活的线程管理。
摘要由CSDN通过智能技术生成

POSIX线程

为实现可移植的线程程序,IEEE在IEEE标准1003.1c中定义了线程的标准。它定义的线程包叫做 Pthread。大部分UNIX系统都支持该标准。这个标准定义了超过60个函数调用,如果在这里列举一遍就太多 了。取而代之的是,我们将仅仅描述一些主要的函数,以说明它是如何工作的。图中列举了这些函数调用
在这里插入图片描述

在用户空间中实现线程

有两种主要的方法实现线程包:在用户空间中和在内核中

第一种方法是把整个线程包放在用户空间中,内核对线程包一无所知。从内核角度考虑,就是按正常的方式管理,即单线程进程

结构:
在这里插入图片描述
在用户空间管理线程时,每个进程需要有其专用的线程表(thread table),用来跟踪该进程中的线程。 这些表和内核中的进程表类似,不过它仅仅记录各个线程的属性,如每个线程的程序计数器、堆栈指针、寄 存器和状态等。该线程表由运行时系统管理。当一个线程转换到就绪状态或阻塞状态时,在该线程表中存放 重新启动该线程所需的信息,与内核在进程表中存放进程的信息完全一样

当某个线程做了一些会引起在本地阻塞的事情之后,例如,等待进程中另一个线程完成某项工作,它调 用一个运行时系统的过程,这个过程检查该线程是否必须进入阻塞状态。如果是,它在线程表中保存该线程 的寄存器(即它本身的),查看表中可运行的就绪线程,并把新线程的保存值重新装入机器的寄存器中。只要堆栈指针和程序计数器一被切换,新的线程就又自动投入运行
优点:

  • 用户级线程包可以在不支持线程的操作系统上实现。过去所有的操作系统都属于这个范围,即使现在也有一些操作系统还是不支持线程。通过这一方法,可以用函数库实现线程
  • 如果机器有一条保存所有寄存器的指令和另一条装入全部寄存器的指令,那么整个线程的切换可以在几条指令内完成。进行类似于这样的线程切换至 少比陷入内核要快一个数量级(或许更多),这是使用用户级线程包的极大的优点
  • 在线程完成运行时,例如,在它调用thread_yield时, pthread_yield代码可以把该线程的信息保存在线程表中,进而,它可以调用线程调度程序来选择另一个要运 行的线程。保存该线程状态的过程和调度程序都只是本地过程,所以启动它们比进行内核调用效率更高。另 一方面,不需要陷阱,不需要上下文切换,也不需要对内存高速缓存进行刷新,这就使得线程调度非常快捷
  • 它允许每个进程有自己定制的调度算法。例如,在某些应用程序中,那些 有垃圾收集线程的应用程序就不用担心线程会在不合适的时刻停止,这是一个长处。用户级线程还具有较好 的可扩展性,这是因为在内核空间中内核线程需要一些固定表格空间和堆栈空间,如果内核线程的数量非常 大,就会出现问题

不足:

  • 其中第一个问题是如何实现阻塞系统调用。假设在还没有任何击键之前,一个线程读取键盘。让该线程实际进行该系统调用是不可接受的,因为这 会停止所有的线程。使用线程的一个主要目标是,首先要允许每个线程使用阻塞调用,但是还要避免被阻塞 的线程影响其他的线程。有了阻塞系统调用,这个目标不是轻易地能够实现的
  • 如果一个线程开始运行,那么在该进程中的其他线程就不能运行,除非第一个线程自动放弃CPU。在一个单独的进程内部,没有时钟中断,所以不可能用轮转调度(轮流)的方式 调度进程。除非某个线程能够按照自己的意志进入运行时系统,否则调度程序就没有任何机会
  • 程序员通常在经常发生线程阻塞的应用中才希望使用多个线程。例如,在多线程Web服务器里。这些线程持续地进行系统调用,而一旦发生内核陷阱进行系统 调用,如果原有的线程已经阻塞,就很难让内核进行线程的切换,如果要让内核消除这种情形,就要持续进 行select系统调用,以便检查read系统调用是否安全。对于那些基本上是CPU密集型而且极少有阻塞的应用程 序而言,使用多线程的目的又何在呢?由于这样的做法并不能得到任何益处,所以没有人会真正提出使用多线程来计算前n个素数或者下象棋等一类工作

在内核中实现线程

如图:
在这里插入图片描述
此时不再需要运行时系统了。另外,每个 进程中也没有线程表。相反,在内核中有用来记录系统中所有线程的线程表。当某个线程希望创建一个新线 程或撤销一个已有线程时,它进行一个系统调用,这个系统调用通过对线程表的更新完成线程创建或撤销工作

内核的线程表保存了每个线程的寄存器、状态和其他信息。这些信息和在用户空间中(在运行时系统 中)的线程是一样的,但是现在保存在内核中。这些信息是传统内核所维护的每个单线程进程信息(即进程 状态)的子集。另外,内核还维护了传统的进程表,以便跟踪进程的状态

优点:

  • 所有能够阻塞线程的调用都以系统调用的形式实现,这与运行时系统过程相比,代价是相当可观的。当 一个线程阻塞时,内核根据其选择,可以运行同一个进程中的另一个线程(若有一个就绪线程)或者运行另 一个进程中的线程。而在用户级线程中,运行时系统始终运行自己进程中的线程,直到内核剥夺它的 CPU(或者没有可运行的线程存在了)为止
  • 内核线程不需要任何新的、非阻塞系统调用。另外,如果某个进程中的线程引起了页面故障,内核可以 很方便地检查该进程是否有任何其他可运行的线程,如果有,在等待所需要的页面从磁盘读入时,就选择一 个可运行的线程运行

不足:

  • 系统调用的代价比较大,所以如果线程的操作(创建、终止等) 比较多,就会带来很大的开销
  • 当一个多线程进程创建新的进程时,会发生什么?新进程是拥有与原进程相同数量的线程,还是只有一个线程?在很多情况下,最好的选 择取决于进程计划下一步做什么。如果它要调用exec来启动一个新的程序,或许一个线程是正确的选择;但 是如果它继续执行,则应该复制所有的线程
  • 信号是发给进程而不是线程的,至少在经典模型中是这样的。当一个信 号到达时,应该由哪一个线程处理它?线程可以“注册”它们感兴趣的某些信号,因此当一个信号到达的时 候,可把它交给需要它的线程。但是如果两个或更多的线程注册了相同的信号,会发生什么?

混合实现

人们已经研究了各种试图将用户级线程的优点和内核级线程的优点结合起来的方法。一种方法是使用内核级线程,然后将用户级线程与某些或者全部内核线程多路复用起来,如图所示。如果采用这种方法, 编程人员可以决定有多少个内核级线程和多少个用户级线程彼此多路复用。这一模型带来最大的灵活度
在这里插入图片描述
采用这种方法,内核只识别内核级线程,并对其进行调度。其中一些内核级线程会被多个用户级线程多 路复用。如同在没有多线程能力操作系统中某个进程中的用户级线程一样,可以创建、撤销和调度这些用户 级线程。在这种模型中,每个内核级线程有一个可以轮流使用的用户级线程集合

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值