线程的简介

线程

线程是一种轻量的进程。它共享进程中的所有资源,包括打开文件描述符、信号、子进程等。。但是也拥有自己的堆栈、寄存器和程序计数器。的程序计数器寄存器堆栈,分别用来记录接着执行哪条指令、保存工作变量、保存了已调用但还未返回的过程。
与进程相比,线程的特点:

  1. 同一进程中的各个线程共享进程的同一地址空间和其他各种资源,包括
  2. 创建线程的速度比较快。
  3. 线程间的切换较进程的切换容易。

线程模型

线程与进程拥有的资源对比如下:

每个进程中的内容每个线程中的内容
地址空间程序计数器
全局变量堆栈
打开的文件描述符寄存器
子进程状态
定时器
信号与信号处理程序
账户信息

在下图中,可以看到三个传统的进程。每个进程含有一个控制线程和自己的地址空间。右边的图则是一个进程含有三个线程的情况,这三个线程共享进程的资源但是拥有自己的堆栈、程序计数器、寄存器。

三个进程一个进程三个线程

每个线程拥有自己的堆栈是因为线程以执行某个函数作为开始,因此不同的线程需要自己的堆栈储存函数调用的相关信息,包括自动变量和函数的返回地址等。
线程含有自己的堆栈

线程有如下常用的操作

  • 创建新的线程
    进程通常以一个线程开始,之后可以创建出更多的线程。
  • 结束线程
    线程完成它的工作之后,会调用系统调用退出,并返回状态。
  • 等待另外一个线程退出
    阻塞以等待另外一个线程退出。
  • 放弃CPU
    线程库无法使用时钟中断强制线程让出CPU,为了防止线程无限占据CPU,因此需要系统调用让线程自动放弃CPU从而让另外一个线程运行。

几种实现

有几种用于实现线程包的方法。分别是

  • 用户空间
  • 内核空间
  • 混合实现

为了能在进程中切换线程,线程也像进程一样需要一个用于保存线程信息的线程表

用户空间

线程在用户空间实现,由用户空间的运行时系统来调度运行。用户空间中每个进程会储存一张线程表,该表含有线程运行所需要信息。
当线程陷入本地阻塞时,如等待另外一个线程的输入,运行时系统会切换线程。切换线程时会保存当前线程状态,接着载入要切换的线程状态,然后执行指令。因为线程实现于用户空间,因此对于内核来说,它并不知道线程的存在,所以不会对线程有任何特殊的处理。
因此他有如下优点:

  1. 切换线程由运行时系统决定,不会用到系统调用,因此比较快。系统调用可能陷入内核、进行上下文切换、刷新高速缓存,因此会比较慢。
  2. 线程的调度完全由运行时系统决定,因此对线程的调度有着极高的可扩展性。

上下文切换:上下文切换指内核在cpu上切换进程或者线程。上下文切换会先保存当前任务寄存器的状态到堆栈中,接着再装载要切换的任务的状态到cpu上(包括程序计数器),然后跳转到程序计数器所指位置执行指令。
上下文切换发生在以下几种情况:

  1. 任务因执行系统调用而陷入阻塞。
  2. 任务正常终止。
  3. 任务用完给它分配的时间片。
  4. 硬件中断。
  5. 用户代码挂起当前任务,比如线程执行yieId()方法,让出cpu

缺点:

  1. 内核不知道线程的存在,当某个线程因系统调用而阻塞的时候,该进程中的所有线程都会陷入阻塞。
    解决方法:1)当线程会因为系统调用而陷入阻塞的时候,会通知运行时系统。运行时系统会利用此消息安排其他的线程工作。2)把系统调用改成非阻塞的。
  2. 若运行的线程发生缺页故障时,其他线程也无法被调度。

缺页故障:程序所执行的指令不在内存中而是在磁盘中,因此需要在磁盘中取回缺失的指令放入内存。

  1. 当一个线程工作时,除非它主动放弃cpu,否则其他线程无法运行。一种可能的解决方法是频繁的时钟中断,但这太耗费系统资源。
内核空间

线程在内核空间实现,线程表存于内核中,由内核调度线程。
优点:

  1. 线程阻塞后,内核可以调用另外一个线程而不是阻塞其他的线程。

缺点:

  1. 切换线程在内核中完成,需要更多的时间。
  2. 创建或撤销线程的代价过大。
    可能解决思路:1)回收线程,当撤销线程时,给它设置一个标志位。之后,创建新的线程可以利用之前使用过的线程。
混合实现

将内核线程与用户空间的线程相结合。
用户空间的线程复用内核线程,内核只会调用内核线程。
混合实现

调度程序激活机制

调度程序模拟机制用于模拟内核线程,但提供了用户空间线程包的灵活和更好的性能。
调度程序模拟机制的几个想法:

  1. 当用户线程使用的系统调用时是安全的,那么不应该使用非阻塞或者提前检查。(这里的安全指的是用户线程所处的状态是安全的)
  2. 当线程陷于内核或者发生缺页故障的时候,如果有那么就应该调度其他线程。
  3. 用户线程因等待另一个线程的输出而阻塞时,在用户空间切换线程即可,不用经过内核。

具体实现:
内核给每个进程分配一个虚拟处理器。
当一个用户线程调用系统函数或缺页导致阻塞时,内核获取这一信息后,会通过已知的地址调用用户运行时系统,并传递用户线程号和事件的描述,运行时系统收到该信息后会进行相应的调度。
当之前阻塞的线程又能够重新运行时,内核以相同的方式通知运行时系统,接着,运行时系统进行调度。
内核调用运行时系统称作上行调用。一般,由上层调用下层的函数,但这里下层调用了上层的函数,这违反了分层次系统的内在结构的概念。

弹出式线程

一个消息的到达导致创建一个新的线程,这种线程称作弹出式线程。

单线程代码多线程化

单线程多线程化使用全局变量会出现竞争,进而导致程序结果错误。
单线程代码转为多线程时有如下问题:

  1. 全局变量。
    解决思路:1)不使用全局变量。这可能会与之前的程序发生冲突。2)为每个线程创建一个唯一的全局变量,线程调用的所有过程都能获得该全局变量。这里提供三种方案:传递内存块、__thread关键字、线程特有数据。
  2. 函数是不可重入的。不可重入指:线程中断返回后,状态与中断前不一致。这可能因为另外一个线程更改了公用的变量。
  3. 堆栈管理。当进程的堆栈溢出时,内核会为进程的堆栈分配更多的空间。但同样的情况不会出现在线程的堆栈上。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值