现代操作系统笔记之 线程

线程其实就像是对进程的进一步划分,每个进程都拥有自己的地址空间,而在这一个地址空间中可能存在多个控制线程。

线程的使用

为什么需要线程?以下是几点原因

1、许多应用中同时有多个活动,某些活动可能会阻塞,将这些活动分配到不同的线程可以使程序设计模型更加简单。

2、线程更加轻量级,创建和撤销比进程更快。

3、如果存在大量计算和大量I/O处理,多线程允许活动重叠进行(流水线),加快活动执行。

4、在多CPU系统中,可以实现真正的并行。

下面参考一个例子,一个字处理软件假设只有一个线程,那么当用户准备对书本进行重新排版的时候,程序不能立即执行,这时候还在等待用户的进一步输入,而全部的交互完成之后,软件处理可能需要大量的等待时间。相反,假设这个程序拥有两个线程,一个负责与用户进行交互,一个负责后台的处理,那么,在用户与软件进行交互的过程中,另一个线程就能够处理大量的工作而不需要用户全部输入完毕,这样就节省了许多时间。当然,也可以同时拥有3个线程,还有一个负责与磁盘交互,随时进行数据的备份,如图:


考虑另一个例子,一个万维网服务器,对页面的请求发送给服务器,所请求的页面发还给客户机。

一种Web服务器的方式如下图所示,一个称为分派程序的线程从网络中读入工作请求,然后挑选一个被阻塞的工作线程,提交该请求,使工作线程处于就绪状态。然后,工作线程检查有关的请求是否在Web页面高速缓存中,如果没有,则该线程开始一个从磁盘调入页面的read操作,并且阻塞直到该操作完成。此时,分派线程可以挑选另一个线程完成其他工作,也可以把另一个当前就绪的工作线程投入运行。


但是,假设没有多线程可以使用,而且又不能忍受单线程造成的低性能,那么可以使用read系统调用的非阻塞版本。当请求到来时,这个唯一的线程就去对请求进行考察,如果该请求能够在高速缓存中得到满足,那么一些都好,如果不能,那么就启动一个非阻塞的磁盘操作。

服务器在当前表格中记录当前请求的状态,然后去处理下一个事件。可能是新工作的请求,或是磁盘对先前操作的回答。如果是新的工作,那么就去处理这个工作,如果是磁盘的回答,那么就从表中取出相应的回答。对于非阻塞磁盘I/O而言,这种回答通常以信号或中断方式进行。这种设计称为有限状态机。

下面可以对这三种情况进行一个对比:


经典的线程模型

进程模型基于两个独立的概念:资源分组处理与执行,对于进程,可以理解为把相关的资源集中在一起,而进程拥有一个执行的线程,简称为线程,该线程拥有寄存器、堆栈。线程是在CPU上被调度的实体。

同一个进程环境中,允许批次之间拥有较大独立性的多个线程执行,多个线程共享同一个地址空间和其他资源,而多进程则是共享物理内存、磁盘、打印机和其他资源,所以线程有时被称为轻量级进程。


进程之间可能存在相互竞争,或是由不同用户打开,所以需要保护,而进程内的线程之间不设保护,因为多线程的存在是为了彼此之间协同完成任务。


上图中,第一项是进程的属性,而非线程的属性。和进程一样,线程也拥有若干种状态:运行、就绪和阻塞,线程之间的切换和进程是一样的,但每个线程都拥有自己的堆栈,如下图,每个线程的堆栈有一帧,供各个被调用但是还没有从中返回的过程使用。在该帧存放了相应过程的局部变量和以及过程调用完成之后的返回地址。例如过程X调用过程Y,而过程Y又调用过程Z,那么当Z执行时,供X/Y/Z使用的帧会全部存在堆栈中。


在多线程的情况下,进程通常会从当前的单个线程开始,这个线程可以调用库函数创建新的线程,线程完成工作时,可以通过调用库函数退出。

POSIX线程

为了维持可移植性定义的标准,定义的线程包叫做Pthread,所有的Pthread线程都含有一个标识符、一组寄存器和一个存储在结构中的属性,这些属性包括堆栈大小、调度参数和线程需要的其他项目。


在用户空间中实现线程

两种方式实现线程包:用户空间和内核

第一种方法是把整个线程包放在用户空间,内核对线程包不可见。从内核来看,就是单线程任务,通过这种方法,可以使用函数库实现。


在用户空间管理线程时,每个进程需要有其专用的线程表,用来跟踪该进程中的线程。这些表与内核中的进程表类似,不过它仅仅记录各个线程的属性。该线程表有运行时系统管理。

当某个线程做了会引起阻塞的任务时,例如等待进程中另一个线程完成某项工作,它调用运行时系统的过程,这个过程检查该线程是否必须进入阻塞状态。如果是,那么它就在线程表中保存该线程的寄存器,查看表中可运行的就绪线程,并把新线程的保存值重新装入到机器寄存器中。只要堆栈指针和程序计数器一切换,新的线程就可以投入运行。

线程和进程有一个关键的差别。在线程完成运行时,例如在调用thread_yield时,pthread_yield代码就可以把该线程的信息保存在线程表中,进而,它可以调用线程调度程序选择另一个要运行的线程。保存该线程的状态和调度程序都只是本地过程,所以启动要比内核调用快得多。

此外,用户级线程允许每个进程有自己的调度算法,具有较好的可扩展性。

但用户级线程也存在一些问题,第一个就是如何实现阻塞系统调用。假设在没有任何击打键盘之前,一个线程读取键盘,而让该线程进行系统调用是不可接受的,这会停止所有的线程。

在内核中实现线程

此时不需要运行时系统,进程中也没有线程表,而在内核中拥有用来记录所有线程的线程表。一个已有线程可以通过一个系统调用对线程表进行更新来创建新的线程。

内核中的线程表的内容与用户空间的类似。

所有能够阻塞线程的调用都以系统调用实现,当一个线程阻塞时,内核可以运行该进程中其他的线程或是运行不同进程中的线程,而在用户空间只能等待CPU分配给另一个进程时才能运行该线程。




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值