【笔记】操作系统(四)——线程

前言

在上次讨论的进程模型假设中,我们默认进程是一个具有单个控制线程的执行程序。但实际上,许多现代操作系统都提供使用单个进程包括多个控制线程的功能。许多常用的软件也是多线程的。比如Web浏览器程序,在运行时它作为一个具有多个控制线程的独立进程实现。在我们使用时,可能一个线程在接收数据,一个线程在显示图像,另一个在读入用户的键盘输入。

一、线程概述

什么是线程?

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

单线程与多线程: 一个传统重量级的进程只有单个控制线程。如果进程有多个控制线程,那么就为多线程进程,它能同时做多个任务。

在这里插入图片描述

为什么要使用线程?

  • 在前言已经提到,对于具有多个任务的独立进程,比如web浏览器,通常使用多线程实现。
  • 一个忙碌的网页服务器可能会有数千个用户同时访问,如果使用传统的单个线程的进程来执行,那么只能一次处理一个请求,客户必须等待很长的处理请求的时间。如果让网页服务器每个用户新开一个进程处理,会导致开销过大,也很耗费时间和资源。

线程有哪些优点?

  1. 响应度高: 如果对一个交互程序采用多线程,那么即使其部分阻塞或执行较冗余的操作,该程序仍能继续进行,从而增加了用户的相应度。
  2. 资源共享: 线程默认共享它们的所属进程的内存和资源。代码和数据共享的优点就是它能允许一个应用程序在同一地址空间有多个不同的活动线程。
  3. 经济: 进程创建所需要的内存和资源的分配会比较昂贵。由于线程能共享它们所属于的进程的资源,所以创建和切换线程会更为经济。
  4. 多处理器体系结构的利用: 多线程的优点之一是能充分使用多处理器体系结构,以便每个进程能并行运行在不同的处理器上。不管有多少CPU,单线程进程只能运行在一个CPU上。在多CPU上使用多线程加强了并发功能。

二、多线程模型

用户线程与内核线程

有两种不同方法来提供线程支持。

用户线程: 用户线程受内核支持,而无须内核管理,用户线程管理由用户级线程库实现。主要的用户级线程库包括:POSIX Pthreads, Win32 threads, Java threads。

内核线程: 内核线程由操作系统直接支持和管理。几乎所用的当代操作系统都支持内核线程。比如WIndows,Linux,Mac OS。

用户线程和内核线程的关系

当用户线程执行时,需要将线程到内核线程才能执行。

多对一模型

多对一模型将许多用户级线程映射到一个内核线程。线程管理是由线程库在用户空间进行的,因而效率比较高。但是如果一个线程阻塞了系统调用,那么整个线程都会阻塞。而且,因为任一时刻都只有一个线程能访问内核,多个线程不能并行运行在多处理器上。多对一模型不限制用户线程数量。
在这里插入图片描述

一对一模型

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

多对多模型

多对多模型多路复用了许多用户线程映射到同样数量或更小数量(因为不可能内核线程大于用户线程数量,这是一种浪费)的内核线程。多对多模型不限制用户线程数量,可并发

如果把用户线程比喻为口渴的人,内核线程为水龙头。那么一对一就是很多人抢一个水龙头,所以不会有两个人同时喝水(并发);一对一就是每个人都有一个水龙头;多对多就是n个人,有小于等于n个水龙头。

在这里插入图片描述

两级模型

可以理解为多对多模型和一对一模型的结合。允许多个用户线程竞争内核线程资源,也允许某些用户线程与内核线程绑定。多对多模型不限制用户线程数量,可并发

在这里插入图片描述

三、线程库

实现线程库的两种方法

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

四、多线程问题

系统调用fork()和exec()

我们以前提到过单线程控制的进程的fork()以及exec()方法,在多线程程序中,系统调用fork()和exec()有所改变。

如果一个在进程中的线程调用fork(),在UNIX系统中,提供了两种处理方式:

  • 一种是复制所有线程
  • 一种是只复制调用了系统调用fork()的线程

线程取消

在网页加载未完毕时,用户可能会之间点击停止按钮以取消进程。

两种常用的方法:

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

信号处理

信号是做什么的?

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

信号所具有的共同点:

  • 信号是由特定事件的发生所发生的。
  • 产生的信号要发送到进程。
  • 一旦发送,信号必须加以处理。
如何处理信号?
  • 当处理信号是在内核中运行的时,可以使用默认信号处理程序。这种默认动作可以用户定义的信号处理程序来改写。
  • 使用用户定义的信号处理程序。
信号会发送到哪里呢?
  • 发送信号到信号所应用的线程。
  • 发送信号到进程内的每个线程。
  • 发送信号到进程内的某些固定线程。
  • 规定一个特定线程以接收进程的所有信号。

线程池

如果没有线程池,服务器每收到一个请求,就要创建一个独立线程。频繁的创建和丢弃线程比较消耗时间。另外,如果请求越来越多,无限制的线程会耗尽系统资源。为了解决这个问题,提出了线程池。

线程池: 线程池的主要思想是在进程开始时创建一定数量的线程,并放入到池中以等待工作。当服务器收到请求时,它会唤醒池中的一个线程(如果有可用的线程),并将要处理的请求传递给它。一旦完成任务,它会返回到池中再等待工作。如果池中没有可用线程,那么服务器会一直等待直到有空线程为止。

线程池的优势:

  • 使用现有线程处理请求要比等待创建新的线程要快。
  • 线程池限制了任何时候可用线程的数量。线程池中的线程数量由系统CPU的数量、物理内存的大小和并发客户请求的期望值等因素决定。

线程特定数据

同属一个进程的线程共享进程数据。事实上,这种数据共享提供了多线程编程的一种优势。但是,在有些情况下每个线程可能需要一定的只属于自己的数据副本,称为线程特定数据,

调度程序激活

许多实现多对多模型或二级模型的系统在用户和内核线程之间设置一种中间数据结构。这种数据结构通常被称为轻量级进程(LWP)

  • 对于用户线程,LWP表现为一种应用程序可以调度用户线程来运行的虚拟处理器
  • 对于内核线程,每个LWP与内核线程相连,该线程被操作系统调度到物理处理器运行。

在这里插入图片描述
调度器激活 :一种解决用户线程库与内核间通信的方法。调度器激活机制提供了upcall,一种通信机制。这种通信机制允许应用程序维护正确数量的内核线程。当一个应用程序要阻塞时,事件引发一个upcall。

猜你喜欢

转载自blog.csdn.net/qq_41882686/article/details/112511578
今日推荐