【操作系统】4.1~4.3 4.4多线程模型和线程库,多线程问题及实验(矩阵乘法,fibonacci序列)

先贴上这章内容的实验链接,两个实验①新建线程,用子进程完成fibonacci序列的计算,父进程输出结果。②完成一个矩阵乘法,最终结果中的每个数应该由一个线程计算,最后等所有子进程计算完成,父进程输出计算结果。

4.1概述

  • 线程是CPU使用的基本单元,它由线程ID,程序计数器,寄存器集合和栈构成。它与属于同一进程的其它线程共享代码段、数据段和其他操作系统资源。

传统重量级单线程,多线程示意图:
这里写图片描述

4.1.1动机

一个应用程序通常是作为一个具有多个控制线程的独立进程实现的。
RPC 通过提供一种类似于普通函数或子程序调用的通信机制,以允许进程通信。通常, RPC 服务器是多线程的。当一个服务器接收到消息,它使用独立线程处理消息。这允许服务器能处理多个并发请求。 Java 的 RMI 系统(远程方法调用)也以类似方式工作。

4.1.2优点

响应度高:例如,线程 Web 浏览器在用一个线程装入图像时,能通过另一个线程与用户交互。
经济:进程创建所需要的内存和资源的分配比较昂贵。由于线程能共享它们所属进程的资源,所以创建和切换线程会更为经济。实际地测量进程创建和管理与线程创建和管理的差别较为困难,但是前者通常要比后者花费更多的时间。
资源共享:线程默认共享它们所属进程的内存和资源。
多处理器体系结构的利用:线程的优点之一是能充分使用多处理器体系结构,
以便每个进程能并行运行在不同的处理器上。不管有多少 CPU,单线程进程只能运行在 一个 CPU 上。在多 CPU 上使用多线程加强了并发功能。

4.2多线程模型

分为用户线程内核线程
用户线程受内核支持,而无须内核管理;丽内核线程由操作系统直接支持和管理。

4.2.1多对一模型

将许多用户级线程映射到一个内核线程。由于任何时刻只能有一个线程可以访问内核,所以一旦这个线程阻塞了,那么整个进程就会阻塞。
这里写图片描述

4.2.2一对一模型

一对一模型将每个用户线程映射到一个内核线程。该模型在–个线程执行阻塞系统调用时,能允许另一个线程继续执行,所以它提供了比多对一模型更好的并发功能:它也允许多个线程能并行地运行在多处理器系统上。
这种模型的唯一缺点是每创建一个用户线程就需要创建一个相应的内核线程。由于创建内核线程的开销会影响应用程序的性能,所以这种模型的绝大多数实现限制了系统所支持的线程数量。
这里写图片描述

4.2.3多对多模型

多对一模型允许开发者创建任意多的用户线程,而一对一模型创建进程须谨慎。多对多模型可创建任意多的用户线程,并且相应内核线程能在多处理器系统上并发执行。而且,当一个线程执行阻塞系统调用时,内核能调度另一个线程来执行
一个流行的多对多模型的变种仍然多路复用了许多用户线程到同样数量或更小数量
的内核线程上,但也允许将 A个用户线程绑定到某个内核线程上。这个变种有时被称为二­级模型:
这里写图片描述

4.3线程库

线程库( thread library )为程序员提供创建和管理线程的 API。
主要有两种方法来实现线程库。

  • 第一种方法是在用户空间中提供一个没有内核支持的库,此库的所有代码和数据
    结构都存在于用户空间中。调用库中的一个函数只是导致了用户空间中的一个本地函数调用,而不是系统调用。
  • 第二种方法是执行一个由操作系统直接支持的内核级的库。此时,库的代码和数据结构存在于内核空间中。调用库中的一个 API 函数通常会导致对内核的习统调用。

目前使用的三种主要的线程库是: (1) POSIX Pthread、 (2) Win32、(3) Java
Pthread作为 POSIX 标准的扩展,可以提供用户级或内核级的库。 Win32 线程库是适用于 Windows操作系统的内核级线程库。 Java 线程 API 允许线程在 Java 程序中直接创建和管理。然而,由于大多数 JVM 实例运行在宿主操作系统之上, Java 线程 API 通常采用宿主系统上的线程库来实现。这意味着在 Windows 系统上, Java 线程通常用 Win32 API 实现,而在 UNIX和 Linux 系统中采用 Pthread。
上面提到的三个线程库这里不做介绍

4.4多线程问题

4.4.1fork()和exec()

fork()的两种形式的使用与应用程序有关。如果调用 fork()之后立即调用 exec() ,那么没有必要复制所有线程,因为 exec()参数所指定的程序会替换整个进程。在这种情况下,只复制调用线程比较适当。不过,如果在 forkO之后另一进程并不调用 exec() ,那么另一进程就应复制所有线程。

4.4.2取消

线程取消 (thread cancellation) 是在线程完成之前来终止线程的任务。

  • 要取消的线程通常称为目标线程。目标线程的取消可在如下两种情况下发生:

    • ①异步取消 (asynchronous cancellation): 一个线程立即终止目标线程。
    • ②延迟取消 (deferred cancellation): 目标线程不断地检查它是否应终止,这允许目标线程有机会以有序方式来终止自己。

    如果资源已分配给要取消的线程或要取消的线程正在更新与其他线所共享的数据,那么取消就会有困难。对于异步取消尤其麻烦。操作系统回收取消线程的系统资源,但是通常并不回收所有资源。因此,异步取消线程并不会使所需的系统资源空闲。相反采用延迟取消时,个线程指示目标线程要被取消,不过,只有当目标线程检查一个标志以确定它是否应该取消时才会发生取消。这允许一个线程检查它是否是在安全的点被取消, Pthread称这些点为取消点 (cancellationpoint)。

4.4.3信号处理

信号在unix中用来通知进程某个特定事件已经发生了。根据需要通知信号的来源和事件的理由,信号可以同步或异步接收。

  • 所有信号具有下列模式:
    • 信号由特定事件的发生所产生。
    • 产生的信号要发送到进程。
    • 一旦发送信号必须加以处理。
  • 同步信号的例子包括非法访问内存和被0所除,同步信号发送到执行操作而产生信号的同一进程。
  • 当一个信号由运行进程之外的事件产生,那么进程就异步接收这个信号。这种信号的例子包括特殊键(ctrl+c)或定时器到期。通常异步信号被发送到另一个进程。
  • 每个信号由两种信号处理方式中的一种来处理:
    • 默认信号处理程序
    • 用户自定义信号处理程序
  • 对于多线程进程信号会发送到哪里呢?
    • 发送信号到信号所应用的线程。
    • 发送信号到进程内的每个线程。
    • 发送信号到进程内的某些固定线程。
    • 规定一个特殊线程以接收进程内的所有信号。

具体发送信号的方法依赖于产生信号的类型。

大多数多线程版 UNIX 允许线程描述它会接收什么信号和拒绝什么信号。因此,有时一个异步信号只能发送给那些不拒绝它的线程。不过,因为信号只能处理一次,所以信号通常发送到不拒绝它的第-个线程。

4.4.4线程池

  • 为什么会出现线程池?
    • 因为虽然创建线程比创建进程要快。但还是会花比较多的时间,并且线程使用完毕丢弃线程也需要时间。
    • 如果所有并发请求都通过新建线程来处理,那么将无法控制系统中并发执行的线程数量。
  • 线程池解决了什么问题?
    • 线程池在进程创建时,创建一定数量的线程,放到池中等待工作。
    • 当服务器收到一个请求时,唤醒线程池中的一个线程,并将要处理的请求传递给他。当线程完成工作后,再把返回到线程池中以等待工作。
  • 线程池具有以下优点:
    • 通常用已有线程处理请求要比建立新的线程要快。
    • 线程池限制了任何时候线程数量,这对于不能支持大量并发线程的系统尤其重要。

4.4.5线程特定数据

同一个进程的线程共享进程数据,事实上,这种共享数据是多线程编程的一个优势,不过有些情况下,线程可能需要一定数量的自己的副本,这种数据成为线程特定数据( thread-specific data)。

4.4.6调度程序激活

一种解决用户线程库与内核间通信的方法被称为调度器激活 (scheduler activation)
它按如下方式工作:

  • 内核提供一组虚拟处理器 (LWP) 给应用程序,应用程序可调度用户
    线程到一个可用的虚拟处理器上。进一步说,内核必须告知与应用程序有关的特定事件。这个过程被称为 upcall(),upcall 由具有 upcall 处理句柄的线程库处理, upcall 处理句柄必须在虚拟处理器上运行。
  • 当一个应用线程将要阻塞时,事件引发-个 upcall。在这个例子中,内核向应用程序发出一个 upcall,通知它线程阻塞并标识特殊的线程。
  • 然后内核分配一个虚拟处理器给应用程序,应用程序在这个新的虚拟处理器上运行 upcall 处理程序,它保存阻塞线程状态和放弃阻塞线程运行的虚拟处理器。
  • 然后 upcall 调度另一个适合在新的虚拟处理器上运行的线程
  • 当阻塞线程事件等待发生时,内核向线程库发出另一个 upcalL来通知它先前阻塞的线程现在可以运行了。此事件的 upcall 处理程序也需要一个虚拟处理器,内核可能分配一个新的虚拟处理器或先占一个用户线程并在其虚拟处理器上运行upcall 处理程序。
  • 在使非阻塞线程可以运行后,应用程序调度符合条件的线程来在一个适当的虚拟处理器上运行。

4.5操作系统实例

  • windows XP使用的是前面讲到的多线程模型中的一对一映射,不过, WindowsXP 也提供了对 fiber 库的支持,该库提供了多对多模型(参见 4.2.3 小节)的功能。
  • 正如第 3 章所讲, Linux 提供了具有传统进程复制功能的系统调用 fork() ,还提供了使用系统调用 clone()创建线程的功能, Linux 并不区分进程和线程。事实上, Linux 在讨论程序控制流时,通常称之为在多而不是进程或线程。 clone()被调用时,它被传递一组标志,以决定父任务与子任务之间发生多少共享。使用这种clone()相当于本章介绍的创建线程,因为父任务和其子任务共享大多数资源。不过,如果当调用clone()时没有设置一个标志,则不会发生共享,导致类似于系统调用fork()提供的功能。
  • 当调用fork()创建新的任务时,它具有父进程的所有数据的副本。当调用系统调用clone()时,也创建了新的任务。不过,并非复制所有的数据结构,根据传递给岛fork()的标志集,新的任务指向父任务的数据结构。

请教各位一些问题,还麻烦各位在评论区解答一下:
1、线程库和多线程模型之间有什么关系啊?

参与评论 您还未登录,请先 登录 后发表或查看评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页

打赏作者

炒扁豆

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

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值