读书笔记-现代操作系统-2进程与线程-2.2线程

2 线程的经典模型

通常来说经典的线程模型和普通的进程模型都是类似的只是在共享资源的问题上有所不同,这里已经放在了上一节中不再详细描述,这里讨论下线程的两种实现方式,这里也是我看书的时候比较难以理解的部分:

2.1 在用户空间中实现线程

这种方法将整个线程包放在用户空间中,而内核对此一无所知。

  • 通用结构:

    • 整个操作系统分为两个部分,内核空间和用户空间,进程当时是分布在内核空间中,在进程内部定义一个运行时系统,这个运行时系统维护线程管理过程,多线程运行在这个运行时系统的基础之上。
    • 每个进程有其专用的线程表,用来跟踪该进程中的线程。该表由运行时系统管理。当线程阻塞后,进程调用一个运行时系统的过程来检测是否需要进入阻塞。如果是则,它在线程表中保存状态信息,查看表中的就绪线程,并试图启动它。
  • 优点:

    1. 可以在不支持线程的操作系统上实现。
    2. 由于不需要陷入内核,调用都是本地调用,速度要比内核实习的快一个数量级左右
    3. 运行为每个进程提供自己定制的调度算法。
  • 缺点:

    1. 如果实现阻塞系统的调用。使用线程的一个主要目标是运行每个线程使用阻塞调用,但是还要避免被阻塞的线程影响其他线程。解决放啊
      • 可以将所有调用改成非阻塞的。但是比较复杂且违反第一条优点
      • 如果一个调用会阻塞,就提前通知,比如有些系统有select,在调用前先调用select来确定是否可以进行阻塞调用,虽然这样重写系统调用库,但是没有更好的方案了。
    2. 页面故障也有类似的问题,(页面故障:由于内存没有一次性存入所有程序,当读到内存没有的程序时需要从硬盘读取,和阻塞类似)
    3. 如果一个线程开始运行,那么该进程中的其他线程就不能运行,除非第一个进程放弃CPU,也就是说在一个单独的进程中没有中断系统,没有办法使用抢占式的调度方式。除非某个线程能够按照自己的以及进入运行时系统,否则调度程序没有任何机会
      • 一个可能的解决方案是运行时系统自己产生时钟中断,但是这对程序来说是并不会,因为不可能总是高频率的产生中断,如果真的做到了也会很大程度的降低效率。
    4. 还有一个最大的问题是用户通常在发生线程阻塞的应用中才使用多线程,如果无法高效方便的解决线程问题,那用户级线程模型的意义何在。

2.2 在内核中实现线程

在内核中有用来记录系统中所有线程的线程表。当某个线程希望创建一个新线程或者撤销一个已有线程时,它进行一个系统调用,这个系统调用通过对线程表的更新完成线程的创建或调用。
内核的线程表,保存每个线程的寄存器、状态和其他信息。这些信息和在用户空间中,但是现在保存在内核中,这些信息是传统内所维护的每个单线程进程信息的子集。当然内核也会维护进程表
所有的阻塞线程的调用都是以系统调用的形式实现,因此代价是很大的。因此在某些系统中不采用频繁创建和撤销线程的方式,而是采用线程池的的方式。
内核线程不需要任何新的、非阻塞系统调用。
当然内核中实现线程也有其他问题,比如说:
线程创建进程时,新进程是只有一个线程还是有多个线程。通常来说取决于洗一遍要作甚。
信号到达,该由那个线程处理也是一个比较有意思的问题。

2.3混合实现

编程人员决定有多少个内核级线程和多少个用户级线程彼此多路复用。
内核只识别内核级线程,其中一些内核级线程会被多个进程多路复用。

2.4 调度程序激活机制

  • 设计思路
    调度程序激活机制的工作目标是模拟内核线程工,但是为线程包提供通常在用户空间中才能实现的更好的性能和更大的灵活性。也就是尽可能避免不必要的调用

    • 特别地,如果用户线程从事某种线程安全的调用时,就不该进行非阻塞调用或者进行提取检测。
    • 如果进行阻塞调用,只要在同一个进程中有任何就行的线程,就应该有可能运行其他的线程。
  • 具体实现

    • 内核给每个进程安排一定数量的虚拟处理器,并且让用户空间的运行时系统将线程分配到处理上。
    • 分配给每个进程的初始处理器数量是一个,但是可以申请更多的处理器,并且在不用时退回。
    • 内核也可取回已经分配出去的处理器,以便分配给其他需要的进程。
    • 内核知道一个线程被阻塞之后,内核通过在一个已知的起始地址启动运行时系统,从而发送了通知,这是对UNIX中信号的一种粗浅模拟,称为上行调用(upcall)
    • 一旦进行上行调用,运行时系统就重新调度其线程。把当前线程标记为阻塞并从就绪表中取出另一线程,并启动它 。稍后当知道原来多现场又可运行的时候,内核就又一次上行调用运行时系统,通知他这一事件。
    • 当中断发送的时候,被中断的CPU切换进核心态,如果被中断的进程对中断事件不感兴趣,那么中断结束后,该进程恢复到原先的状态,如果对中断事件感兴趣,那么被中断的线程挂起,而非进程挂起,再由运行时系统决定该调用哪个线程。

调度程序激活机制的一个目标是作为上行调用的信赖基础,这是违反系统内在结构的概念。通过n层调用n+1层程序

2.5 弹出式线程

传统的方法是将进程或线程阻塞在一个receive系统调用上,等待消息到来。当消息到达时,该系统调用接受消息,并打开消息检测内容然后处理

一个消息到达到,导致系统创建一个处理该消息的线程,这中线程称为弹出式线程,每个线程重新开始,每一个线程彼此之间都完全一样。这样就可以快速创建线程。消息到达和处理开始之间的时间非常短。

但是在使用弹出式线程之前,需要提前计划,可以运行在内核或者用户空间,通常运行在内核更快且容易。但是如果出错问题更大

2.6 单线程代码转换成多线程代码

  • 全局变量
    由于对线程来说是全局变量但是对于整个程序未必是全局函数,还有一些全局变量对一部分线程有影响对其他线程无影响
    • 禁止全局变量
    • 为每个线程赋予其私有的全局变量,相当于创建了新的作用域层。但是大部分语音没有所谓的中间层形式,只有全局变量和局部变量
    • 为全局变量分配一块内存,然后将引用传递给线程。
    • 引用新的库过程,以创建、设置和读取这些线程范围的全局变量。(个人感觉相当于采用库调用的方式实现第二种方案)
  • 不可重用的库过程
    很多库过程是不可以重用的,就是说在尚未调用结束之前不可调用。
    • 可以重写库过程
    • 为每个过程提供一个包装器,该包装器设置一个二进制位从而标准该过程是否在使用。
  • 信号
    有些信号是线程专用的,有些则不是,当线程完全在用户空间实现时,内核根本不知道有线程存在,因此很难将信号发送给正确的线程。还有一些中断不是线程专用的
  • 堆栈管理
    内核根据进程增加堆栈,但是如果一个进程有多个线程,则可能出现混乱。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值