操作系统哲学原理(07)线程原理-线程

说明:该系类文章更多的是从从哲学视角看 操作系统 这门学科。同时也是 操作系统的学习笔记总结。因为博主 这些年主要是以研究安卓系统和 嵌入式Linux为主,因此这个系类文章也是这两个领域不可或缺的基石之一,尤其是对操作系统感兴趣的伙伴可特别关注。


  • 进程只有一个执行序列,从根本上讲并不能满足所有并发的奢望。有了并发,我们希望的是并发能帮助我们实现在非并发时难以实现/实现成本高昂的功能;并发的个体通过合作完成更加复杂的任务。
  • 虽然可以使用诸多进程来相互协作实现需要并发才能完成的功能,但是进程间的协作有着重要的限制:每个进程都有自己的独立空间,这种限制导致进程间的协作出现缺陷,如果需要共享数据则很难,于是想到了进程内并发,而这就是线程出现的动机。 
  • 虽然进程和线程都是并发,但是并发的层次不一样,进程属于处理器并发,即处理器提供的抽象;线程则属于进程级的并发,即在进程这个层次上提供一层并发的抽象。
  • 注意:实际上流水线也是一种并发,不过是指令级并发。这样,流水线、进程、线程从低到高在3个层次上提供了我们需要的并发。

7 线程

7.1 进程的分身-线程

线程是进程的分身,每个线程本身是一样的,都有同样的程序文本,但是执行时,上下文(执行序列)不一致。一个进程可以有多个执行序列,即多个线程。线程模式如下,一个进程至少要有一个线程:

将进程分解为线程是可以高效利用多核处理器和多核计算机。在没有线程的情况下,多一个处理器不能使一个进程本身执行速度提高,但是分为多个线程,则可以让不同的线程同时运转在不同的处理器上,从而提高执行效率。

7.2 线程管理

有了进程,要管理进程;有了线程,自然要管理线程。而管理的方式也与进程管理的基础类似,就是要维持线程的各种秩序。存放线程信息的数据结构称为线程控制表/控制块。对于线程而言,共享的资源放在进程控制块;但由于线程是不同的执行序列,总有不能共享的资源,这些不共享的资源就放在线程控制块里。    对于哪些资源不共享,哪些共享要按照这个标准来看:如果该资源不独享会导致线程运行错误,那么该资源就由每个线程独享;而其他资源都由进程里面所有线程独享。按照这个标准线程共享资源和不共享资源的划分如下:

7.3 线程模型的实现

线程模型是在进程的基础上提供第二次开发,线程之间的共享远比进程间的共享要丰富,因此需要在高度共享的环境下发挥其重要作用。

  • 在存储上,线程依附于进程,无需额外设计。
  • 在调度上,由于线程是在进程的基础上实现的,所以可以由进程来调度;同样,也可以交给操作系统;即线程的实现可以是在内核态,也可以在用户态。(进程的实现方式只有内核态实现,因为进程是在CPU上并发的,而CPU由操作系统来管理,因此进程在实现上不涉及用户态)。    

7.3.1 内核态线程实现

  • 利用操作系统来管理线程的优点:用户编程方便,无需考虑调度问题。    
  • 利用操作系统来管理线程的缺点:执行效率低下(每次都要陷入内核态,这需要切换,也就需要时间);而且内核空间的资源有限,存在很大风险;而操作系统又不能因此大改,因为涉及商业成本问题。

7.3.2 用户态线程实现

  • 利用进程来管理线程的优点:灵活性高,切换速度快,(甚至操作系统无需知道线程的存在,所以在所有操作系统上都能应用)。    
  • 利用进程来管理线程的缺点:编写程序变得诡异、困难(因为需要自己考虑线程调度);如果线程受阻,进城就无法推进(因为受阻后无法执行交出CPU的指令,这也是致命的弱点);而对此解决问题的方法也因为缺陷太多而得不到商用操作系统的认可。

7.4 现代操作系统的线程实现模型

由于用户态和内核态的线程模型都存在缺陷,因此现代操作系统将两者结合起来使用。用户态的执行系统负责进程内部线程在非阻塞时的切换;内核态的操作系统负责阻塞线程的切换,即同时实现内核态和用户态的线程管理;同时内核态线程较少,用户态线程较多,即一个内核态线程可以服务多个用户态线程,即用户态线程被多路复用到内核态线程上,线程的内核态和用户态混合实现如图:

5个线程被分成2组,一组2个,一组3个;每组线程使用一个内核线程,即该进程使用2个内核线程,如果一个线程阻塞,则同组的线程均阻塞,但另一组线程可以继续执行;这样,在分配线程时,我们尽可能地将需要执行阻塞操作的线程设为内核态线程,而不会执行阻塞操作的线程设为用户态线程。这样就可以利用内核态与用户态的优点而避免缺陷。

7.5 多线程的关系

多个线程共享一个地址空间,那么也必然会因为争夺资源而产生矛盾,这些矛盾归结为两个根本问题:

  • 线程间如何通信?
  • 线程间如何同步?

不只是线程,进程之间也存在这样的问题。

7.6 从用户态到内核态

如果程序在运行中发生中断/异常,系统将会自动切换到内核态运行中断/异常处理机制。中断导致态势的切换流程如下:

此外,程序进行系统调用也将从用户态陷入到内核态,实际上任何程序在编写的时候最终都会使用系统调用,下面是一个CPP调用cin的例子: 一个CPP程序调用诵数cin,cin是一个标准库函数;它将调用scanf()库函数,scanf()库函数会进一步调用read函数而read是由操作系统提供的一个系统调用。其执行过程如下:

  • 1)执行汇编语言里面的系统调用指令(如syscall)。
  • 2)将调用的参效sys_read.file number ,size存放在指定的寄存器或栈上(事先约好)。
  • 3)当处理器执行到“syscall”指令时.察觉这是一个系统调用指令.将进行如下操作:
  •      3.1)设置处理器至内核态。
  •      3.2)保存写前寄存器(栈指针、程序计数器、通用寄存器)。
  •      3.3)将栈指针设置指向内核栈地址。
  •      3.4)将程序计数器设置为一个事先约定的地址上,该地址上存放的是系统调用处理程序的起始地址。
  • 4)系统调用处理程序执行系统调用。并调用内核里而的read函数,这样.就实现了从用户态到内核态的转换.并且完成系统调用所要求的功能。

7.7 线程的困惑:确定性与非确定性

线程的优点:

  • 实现进程内部的并发,在进程级别上实现多道编程。
  • 提高了程序运行的效率与硬件资源的使用率与系统的吞吐率。    

线程的缺点:                        

带来了系统运行的不确定性(对于进程而言,系统不确定性体现在程序执行的先后顺序上,每个程序本身的运行结果是确定的;而线程却带来了程序本身运行的不确定性,同步机制可以改善这种不确定性,但是如果多线程执行过程中出现异常,则情况相当麻烦)

线程问题类似于多级流水线问题,只不过 流水线是指令级的。并发提高了计算机的吞吐率,改善了用户的响应时间,但由于多指令在不同的流水线和梯级上执行,其之间存在的数据和指令以来关系也会十分复杂;如果万一发生异常,如何保存一个一致性状态都会成为问题。以下是一个流水线的指令级并发和线程的程序并发的对照。

更为给重要的是线程和流水线的管理都十分复杂。线程的同步机制复杂,而这使得整个操作系统变得复杂,从而增加操作系统的不可靠性(因为复杂的东西其可靠性都是很难保证的),而这种复杂性是否值得,这要看需求,不容易回答。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值