操作系统-进程和线程的总结

如果你觉得内容对你有帮助的话,不如给个点赞+收藏,鼓励一下更新:joy:
更多文章移步 => Linn-cn

操作系统的设计,可以归结为三点:

  • 以多进程形式,允许多个任务同时运行;

  • 以多线程形式,允许单个任务分成不同的部分运行;

  • 提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

进程和线程的区别

  • 定义:进程是应用的执行副本
  • 进程是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间,至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。
  • 线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程
  • 一个进程在其执行的过程中可以产生多个线程。

进程和线程上下文切换区别

  • 线程上下文切换和进程上下文切换一个最主要的区别是 线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的
  • 进程上下文切换涉及切换内存地址空间。这包括内存地址,映射,页表和内核资源 - 这是一个相对昂贵的操作。
  • 线程切换是指在同一进程中从一个线程切换到另一个线程(跨线程切换仅仅是进程切换),切换**处理器状态(例如栈、程序计数器和寄存器内容)**通常非常有效。

创建进程的开销比线程大在了哪里?

Linux 中创建一个进程自然会创建一个线程,也就是主线程。创建进程需要为进程划分出一块完整的内存空间,有大量的初始化操作,比如要把内存分段(堆栈、正文区等)。创建线程则简单得多,只需要确定 PC 指针和寄存器的值,并且给线程分配一个栈用于执行程序,同一个进程的多个线程间可以复用堆栈。因此,创建进程比创建线程慢,而且进程的内存开销更大。

进程间的通信方式

单机

  • 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  • 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  • 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

速度快,不好写程序,一种侵入式的开发

  • 信号量(semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  • 信号 (signal) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

分布式

  • 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

如果考虑分布式模型,就有远程调用、消息队列和网络请求。直接发送网络请求程序不好写,不如直接用实现好的 RPC 调用框架。RPC框架会增加系统的耦合,可以考虑消息队列,以及发布订阅事件的模式,这样可以减少系统间的耦合。

线程间的通信方式

  • 锁机制:包括互斥锁、条件变量、读写锁
    • 互斥锁提供了以排他方式防止数据结构被并发修改的方法。
    • 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
    • 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
  • 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
  • 信号机制(Signal):类似进程间的信号处理

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

用户态线程和内核态线程

内存空间

内核空间(Kernal Space):这个空间只有内核程序可以访问。
用户空间(User Space):这部分内存专门给应用程序使用。

进程在系统上的运行分为两个级别:用户态和内核态

用户空间中的代码被限制了只能使用一个局部的内存空间,我们说这些程序在用户态(User Mode) 执行。内核空间中的代码可以访问所有内存,我们称这些程序在内核态(Kernal Mode) 执行。

用户态线程的优点和缺点

用户级线程有很多优势:

  • 管理开销小:创建、销毁不需要系统调用。
  • 切换成本低:用户空间程序可以自己维护,不需要走操作系统调度。

缺点:

  • 与内核协作成本高:比如这种线程完全是用户空间程序在管理,当它进行 I/O 的时候,无法利用到内核的优势,需要频繁进行用户态到内核态的切换。
  • 线程间协作成本高:设想两个线程需要通信,通信需要 I/O,I/O 需要系统调用,因此用户态线程需要支付额外的系统调用成本。
  • 无法利用多核优势:比如操作系统调度的仍然是这个线程所属的进程,所以无论每次一个进程有多少用户态的线程,都只能并发执行一个线程,因此一个进程的多个线程无法利用多核的优势。
  • 操作系统无法针对线程调度进行优化:当一个进程的一个用户态线程阻塞(Block)了,操作系统无法及时发现和处理阻塞问题,它不会更换执行其他线程,从而造成资源浪费。

内核态线程的优点和缺点

可以通过系统调用创造一个内核级线程。
内核级线程有很多优势:

  • 可以利用多核 CPU 优势:内核拥有较高权限,因此可以在多个 CPU 核心上执行内核线程。
  • 操作系统级优化:内核中的线程操作 I/O 不需要进行系统调用;一个内核线程阻塞了,可以立即让另一个执行。

当然内核线程也有一些缺点。

  • 创建成本高:创建的时候需要系统调用,也就是切换到内核态。
  • 扩展性差:由一个内核程序管理,不可能数量太多。
  • 切换成本较高:切换的时候,也同样存在需要内核操作,需要切换内核态。

用户态线程和内核态线程之间的映射关系

多对一(Many to One)

用户态进程中的多线程复用一个内核态线程。

一对一(One to One)

该模型为每个用户态的线程分配一个单独的内核态线程

多对多(Many To Many)

这种模式下会为 n 个用户态线程分配 m 个内核态线程。m 通常可以小于 n。

两层设计(Two Level)

这种模型混合了多对多和一对一的特点。多数用户态线程和内核线程是 n 对 m 的关系,少量用户线程可以指定成 1 对 1 的关系。

用户态线程和内核态线程的区别?

用户态线程工作在用户空间,内核态线程工作在内核空间用户态线程调度完全由进程负责,通常就是由进程的主线程负责。相当于进程主线程的延展,使用的是操作系统分配给进程主线程的时间片段。内核线程由内核维护,由操作系统调度
用户态线程无法跨核心,一个进程的多个用户态线程不能并发,阻塞一个用户态线程会导致进程的主线程阻塞,直接交出执行权限。这些都是用户态线程的劣势。内核线程可以独立执行,操作系统会分配时间片段。因此内核态线程更完整,也称作轻量级进程。内核态线程创建成本高,切换成本高,创建太多还会给调度算法增加压力,因此不会太多。

Java中创建的线程是用户级还是内核级

JVM 自己本身有一个线程模型。在 JDK 1.1 的时候,JVM 自己管理用户级线程。这样做缺点非常明显,操作系统只调度内核级线程,用户级线程相当于基于操作系统分配到进程主线程的时间片,再次拆分,因此无法利用多核特性。
为了解决这个问题,后来 Java 改用线程映射模型,因此,需要操作系统支持。在 Windows 上是 1 对 1 的模型,在 Linux 上是 n 对 m 的模型。映射关系是操作系统自动完成的,用户不需要管。

用户态线程跟内核态线程通过内核线程的高级接口(LWP)形成一对一的关系,内核线程通过(LWP)把时间片给用户线程,当发生系统调用的时候,用户态线程通过LWP完成用户态到内核态的切换。

综上,java线程是混合型的线程模型。

参考

最后别忘了点赞哦!!!:cat::eyeglasses:

2 Likes

:cat::cat::cat::cat::cat:

:zipper_mouth_face:咦,我的表情包咋变了,好像不支持微软输入法的小表情?

:cat:

:rofl: :rofl: