操作系统——深入理解进程和线程

1.进程和线程的定义

进程:是执行中一段程序,一个程序被载入到内存中并准备执行,它就是一个进程,是系统进行资源分配和调度的一个基本单位。

线程:是进程的一个实体,是cpu调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,有时被称为轻量级进程。

2.进程和线程的区别

  1. 同一个进程可以包含多个线程,一个进程中至少包含一个线程,一个线程只能存在于一个进程中。即线程必须依托于进程。

  2. 同一进程下的各个线程并不是互相独立的,需要共享进程的资源。而各个进程基本上独立的,并不互相干扰。

  3. 线程是轻量级的进程,它的创建和销毁所需要的时间和资源消耗相比进程比进程小得多

  4. 在操作系统中,进程是拥有系统资源分配和调度的独立单元,它可以拥有自己的资源。一般而言,线程不能拥有自己的资源,但是它能够访问其隶属进程的资源,线程是CPU分派和调度的基本单位

3.进程间的通信方式

3.1 概述

之前说过,进程间是相互独立的,但是有时候为了共同完成一组任务需要各个进程相互合作,这就需要各个进程之间进行数据传输或者资源共享,因此需要进程间通信(IPC Interprocess Communication)

3.2 目的

  1. 数据传输:各个进程之间需要交换传输数据
  2. 共享数据:各个进程需要操作共享数据,一个进程对其修改别的进程应该立马看到
  3. 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  4. 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

3.3 方式-七种

1.管道/匿名管道(pipe)

操作系统——深入理解进程和线程

特点

  • 半双工
  • 亲缘关系
  • 单独构成一种文件系统,并且只存在与内存中。
  • 队列形式的读写(FIFO)

缺点(从特点上说!)

  • 半双工
  • 亲缘关系
  • 存于内存,大小受限
  • 无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等

2.有名管道

哦,貌似加了名字(准确来说是将一个路径名与之关联)!这就是最主要的区别,这样不同进程即使无亲缘关系也可以互相通信啦!其他都一样~ 强调一下有名管道的名字存在于文件系统中,内容存放在内存中。

3.信号(signal)

是Unix系统中使用的最古老的进程间通信的方法之一。操作系统通过信号来通知进程系统中发生了某种预先规定好的事件(一组事件中的一个),它也是用户进程之间通信和同步的一种原始机制。

操作系统——深入理解进程和线程
信号的生命周期

Linux系统中常用信号:
(1)SIGHUP:用户从终端注销,所有已启动进程都将收到该进程。系统缺省状态下对该信号的处理是终止进程。
(2)SIGINT:程序终止信号。程序运行过程中,按Ctrl+C键将产生该信号。
(3)SIGQUIT:程序退出信号。程序运行过程中,按Ctrl+\\键将产生该信号。
(4)SIGBUS和SIGSEGV:进程访问非法地址。
(5)SIGFPE:运算中出现致命错误,如除零操作、数据溢出等。
(6)SIGKILL:用户终止进程执行信号。shell下执行kill -9发送该信号。
(7)SIGTERM:结束进程信号。shell下执行kill 进程pid发送该信号。
(8)SIGALRM:定时器信号。
(9)SIGCLD:子进程退出信号。如果其父进程没有忽略该信号也没有处理该信号,则子进程退出后将形成僵尸进程。

进程可以对任何信号指定另一个动作或重载缺省动作,指定的新动作可以是忽略信号。进程也可以暂时地阻塞一个信号。因此进程可以选择对某种信号所采取的特定操作,这些操作包括:

  • 忽略信号:进程可忽略产生的信号,但 SIGKILL 和 SIGSTOP 信号不能被忽略,必须处理(由进程自己或由内核处理)。进程可以忽略掉系统产生的大多数信号。
  • 阻塞信号:进程可选择阻塞某些信号,即先将到来的某些信号记录下来,等到以后(解除阻塞后)再处理它。
  • 由进程处理该信号:进程本身可在系统中注册处理信号的处理程序地址,当发出该信号时,由注册的处理程序处理信号。
  • 由内核进行缺省处理:信号由内核的缺省处理程序处理,执行该信号的缺省动作。例如,进程接收到SIGFPE(浮点异常)的缺省动作是产生core并退出。大多数情况下,信号由内核处理。

4.消息队列(message queue)

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

特点
  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势。
  • 消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。

5.共享内存(share memory)

操作系统——深入理解进程和线程

特点

  • 直接读写同一块内存
  • 地址映射,无需拷贝
  • 需要同步机制来确保共享内存的安全性

6.信号量(semaphore)

信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。就是操作系统之前提到过的 P V 原语操作(生产者消费者!)

信号量与互斥量之间的区别:
(1)首先互斥量用于线程的互斥,信号量用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的一定顺序的访问。可以通过信号量实现!
(2)互斥量值只能为0/1,信号量值可以为非负整数。
一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量时,也可以完成一个资源的互斥访问。

7.套接字(socket)

4.线程间的通信方式

  1. 互斥量(Mutex):采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
  2. 信号量(Semphares) :它允许同一时刻多个线程访问多个同类资源,但是需要控制同一时刻访问此资源的最大线程数量
  3. 事件(Event) :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操

5.进程的底层实现

6.线程的底层实现

** 主流的操作系统都提供了线程的实现**,注意这句话,谁实现的线程?是操作系统,实际上实现线程的老大哥,是运行在内核态的操作系统。

6.1操作系统实现线程主要有 3 种方式

  • 用户级线程(非主流)
  • 内核级线程(主流)
  • 用户级线程 + 内核级线程,混合实现(非主流)

6.2内核级线程

内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身。

用户进程一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间 1 : 1 的关系称为一对一的线程模型,如下图所示。

在这里插入图片描述
由于有内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作,但是轻量级进程具有它局限性,主要有如下两点

  • 线程的创建、销毁等操作,都需要进行系统调用,而系统调用的代价相对较高,需要在用户态和内核态之间来回切换。
  • 每个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(比如内核线程的栈空间),因此一个系统支持轻量级线程的数量是有限的。

6.2用户级线程

用户级线程的实现就是把整个线程实现部分放在用户空间中,内核对线程一无所知,内核看到的就是一个单线程进程。

对于线程的底层实现,现在很少有操作系统会使用单纯的用户级线程这中线程模型来实现。

注:对于实现的是用户级线程的操作系统而言,CPU 调度的基本单位看起来像是进程(因为在内核看来,这些进程都是单线程的,所以对单线程的调度就像是在调度进程一样)。

在这里插入图片描述

使用用户线程的优势在于不需要内核支援,劣势也在于没有内核的支援,所有的线程操作都需要用户程序自己处理。线程的创建、切换和调度都是需要考虑的问题。因而使用用户线程实现的程序都比较复杂,除了以前在不支持多线程的操作系统中的多线程程序与少数有特殊需求的程序外,现在使用用户线程的程序越来越少了。

6.3二者的对比

关于用户级线程和内核级线程这两种线程模型的对比,个人认为主要可以从调度、开销、性能这三个角度来看待。

  • 调度:对于用户级线程,操作系统内核不可感知,调度需要由开发者自己实现,内核级线程则与之相反,开发者可以做个甩手掌柜,将调度全权交由操作系统内核来完成。
  • 开销:在前面介绍用户级线程的优点时,也提到了,在用户空间创建线程的开销相比之下会比内核空间小很多。
  • 性能:用户级线程的切换发生在用户空间,这样的线程切换至少比陷入内核要快一个数量级,不需要陷入内核、不需要上下文切换、不需要对内存高速缓存进行刷新,这就使得线程调度非常快捷。

在早期的操作系统中有不支持线程的,都是使用用户线程来实现的,现在都支持线程了,大多数都使用轻量级进程去映射内核线程的手段来实现多线程技术,包括常见的 Windows 和 Linux 就这种一对一的线程模型。

7.进程间/线程间的上下文切换

推荐阅读:上下文切换

8.操作系统进程的调度算法

  • 先到先服务(FCFS)调度算法 : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  • 短作业优先(SJF)的调度算法 : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  • 时间片轮转调度算法 : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
  • 多级反馈队列调度算法 :前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
  • 优先级调度 : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。

9.协程和线程的比较

协程是一种用户态的轻量级线程,又称”微线程”,协程的调度完全由用户控制。以下为两者比较:

  1. 协程的执行效率非常高。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销
  2. 协程不需要多线程的锁机制。在协程中控制共享资源不加锁,只需要判断状态就好了。线程和进程是同步的,协程是异步的!
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!