进程、线程、协程

进程、线程、协程

  • 进程、线程、协程
    • 进程
      • 概念
      • 生命周期
      • 进程的五状态模型
      • 进程同步机制
      • 进程通信机制
      • 死锁
      • 进程调度算法
    • 线程
      • 概念
      • 生命周期
      • 线程同步机制
        • 互斥锁
        • 信号量
        • 条件变量
        • 读写锁
      • 线程通信机制
      • 线程死锁
    • 协程
    • 进程、线程、协程对比
      • 进程与线程比较
      • 协程与线程比较
    • 如何选择进程、线程、协程?

进程、线程、协程

进程

概念

进程是资源分配的最小单位,是最小的资源管理单元。

直白地讲,进程就是应用程序的启动实例。比如我们运行一个游戏,打开一个软件,就是开启了一个进程。

每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

生命周期

当程序需要运行时,操作系统将代码和所有静态数据记载到内存和进程的地址空间(每个进程都拥有唯一的地址空间,见下图所示)中,通过创建和初始化栈(局部变量,函数参数和返回地址)、分配堆内存以及与IO相关的任务,当前期准备工作完成,启动程序,OS将CPU的控制权转移到新创建的进程,进程开始运行。

在这里插入图片描述

PCB(Processing Control Block):操作系统对进程的控制和管理通过,PCB通常是系统内存占用区中的一个连续存区,它存放着操作系统用于描述进程情况及控制进程运行所需的全部信息(进程标识号、进程状态、进程优先级、文件系统指针以及各个寄存器的内容等),进程的PCB是系统感知进程的唯一实体。

进程的五状态模型

一个进程至少具有5种基本状态:创建态、执行状态、等待(阻塞)状态、就绪状态、终止状态。

  • 创建状态:进程刚被创建需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。此时进程刚被创建,由于其他进程正占有CPU所以得不到执行,只能处于初始状态。
  • 就绪状态:进程已经准备好,且已分配到所需资源,只要分配到cpu就能立即运行。
  • 执行状态:进程处于就绪状态的经过调度才能到执行状态。任意时刻处于执行状态的进程只能有一个。
  • 阻塞状态:正在执行的进程由于某些事件(IO请求,申请缓存区失败)而暂时无法运行,进程受到阻塞,在满足请求时进入就绪状态等待系统调用。
  • 终止状态:进程结束或出现错误或系统终止,进入终止状态,无法再执行。

在这里插入图片描述

进程同步机制

进程同步机制的主要任务是对多个相关的进程在执行次序上进行协调,使并发执行的诸多进程之间能够按照一定的规则共享系统资源,并能很好的相互合作,从而使程序之间的执行具有可再现性。

进程间的两种制约关系:

  1. 间接相互制约(互斥):因为进程在并发执行的时候共享临界资源而形成的相互制约的关系,需要对临界资源互斥地访问。

  2. 直接制约关系(同步):多个进程之间为完成同一任务而相互合作而形成的制约关系。

临界资源:同一时刻只允许一个进程可以访问的资源。各个进程之间采取互斥方式,实现对临界资源的共享。

临界区:进程中访问临界资源的那段代码。

同步机制应遵循的规则:

  1. 空闲让进:当临界区的“大门”敞开时,应当允许一个请求的进入临界区的进程立即进入临界区。

  2. 忙则等待:当临界区的“大门”关闭时,因而其他试图进入临界区的进程必须等待,以保证对临界资源的互斥访问。

  3. 有限等待:对要求进入临界区的进程,应保证有限的时间能进入自已的临界区,以免陷入“死等” 状态。

  4. 让权等待:当进程不能进入自已的临界区时,应立即释放处理机,以免进程陷入“忙等”状态。

“忙等 ”和 “死等” 都是没能进入临界区,它们的区别如下:

  1. 死等: 对行死等的进程来说,这个进程可能是处于阻塞状态,等着别的进程将其唤醒(signal 原语),但是如果唤醒原语一直无法执行,对于阻塞的进程来说,就是一直处于死等的状态,是无法获得处理机的。
  2. 忙等:忙等状态比较容易理解,处于忙等状态的进程是一直占有处理机去不断的判断临界区是否可以进入,在此期间,进程一直在运行,这就是忙等状态。有一点需要注意的是,忙等是非常可怕的,因为处于忙等的进程会一直霸占处理机的,相当于陷入死循环了。 忙等的状态在单CPU 系统中是无法被打破的,只能系统重启解决。

进程通信机制

详见于:《进程间的通信方式》

死锁

详见于:《操作系统:死锁》

进程调度算法

详见于:《进程调度算法》

线程

概念

线程是CPU调度的最小单位,是最小的执行单元。 线程又叫做轻量级进程,线程从属于进程,是程序的实际执行者。

  • 一个进程至少包含一个主线程,也可以有更多的子线程。
  • 多个线程共享所属进程的资源,同时线程也拥有自己的专属资源、拥有自己的栈空间。
  • 线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

一个进程中的各个线程都有共享的资源而且是完全开放的,那么在进程运行中会出现多个线程访问同一个公共资源的问题。这种现象我们称之为线程之间产生了资源竞争,这种竞争会导致程序异常甚至崩溃。

生命周期

线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、死亡。当线程进入运行状态后,一般的操作系统是采用抢占式的方式来让线程获得CPU。所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞、就绪之间切换。

  1. 新建:使用new方法,new出来线程,此时仅仅由JAVA虚拟机为其分配内存,并初始化成员变量的值。此时仅仅是个对象。
  2. 就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;该线程进入就绪状态,JAVA虚拟机会为其创建方法调用栈和程序计数器。线程的执行是由底层平台控制, 具有一定的随机性。
  3. 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;(当处于就绪状态的线程获得CPU,它就会执行run()方法)。对于一个单核cpu(或者是一个内核)来说,只能同时执行一条指令,而JVM通过快速切换线程执行指令来达到多线程的,真正处理器就能同时处理一条指令,只是这种切换速度很快,我们根本不会感知到。为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。当一个线程开始运行后,它不可能一直持有CPU(除非该线程执行体非常短,瞬间就执行结束了)。所以,线程在执行过程中需要被中断,目的是让其它线程获得执行的CPU的机会。线程的调度细节取决于底层平台所采用的策略。
  4. 阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态。原因如下:
    • 等待I/O流的输入输出
    • 等待网络资源,即网速问题
    • 调用sleep()方法,需要等sleep时间结束
    • 调用wait()方法,需要调用notify()唤醒线程
    • 其他线程执行join()方法,当前线程则会阻塞,需要等其他线程执行完。
  5. 死亡:
    • run()/call()方法执行完成,线程正常结束;
    • 线程抛出一个未捕获的Exception或Error;
    • 直接调用线程的stop()方法结束该线程——该方法容易导致死锁,通常不建议使用。

线程同步机制

线程同步四种方法:互斥锁、信号量、条件变量、读写锁。

互斥锁

互斥锁又称互斥体或互斥量,是最简单的一种线程同步机制。

顾名思义,当一个线程访问的时候,就会把资源“锁”上,直到访问结束才会解锁,给其他线程访问。互斥锁本身就是一个全局变量:unlock 和 lock。unlock表示当前资源可以访问,第一个访问资源的线程将互斥锁修改为lock,访问完以后再修改为unlock;lock表示线程正在访问资源,其他线程需要等待互斥锁的值为unlock才能继续访问。

该线程负责的加锁,解锁也需要该线程。

信号量

信号量控制同时访问公共资源的线程数量。当线程数量小于等于1时,这种信号量可以叫二元信号量,同理多的时候,叫多元信号量,是指同一时刻最后只有这么多个线程可以访问该资源。

信号量的取值范围必须>=0;值得一提的是信号量可以执行加一和减一的操作,而且这种操作还是原子操作来实现的。

原子操作,你可以理解为多个线程修改信号量,但是在修改值的过程中互不干扰。

具体操作流程:

  1. 线程访问资源时,信号量减一,访问完成加一。
  2. 信号量为0时候,其他访问线程需要等待,直到大于0。

信号量分类:

  • 二元信号量:初始值为1,信号量的值只有0和1,一定程度上替代互斥锁进行线程同步。
  • 计数信号量:初始值大于1,可以允许多个线程同一时间访问同一资源,起到限制访问个数的作用。
条件变量

功能类似现实中的门,只有打开和关闭两种状态,对应条件中的成立与不成立两种判定,一旦关闭,所有线程都不得访问该资源,一旦打开,那就恢复执行。通常,条件变量和互斥锁是搭配使用的。

条件变量的本质也是全局变量,它的功能是阻塞线程,直到收到条件成立的信号,被阻塞的程序才能继续执行。

具体流程:

  1. 阻塞线程,直到收到信号
  2. 向等待队列中一个或者所有线程发送条件成立的信号,解除被阻塞的状态。

为了避免多线程抢资源的情况发生,条件变量必须和互斥锁搭配使用。

读写锁

如果很多线程只是进行读取操作,只有少部分是写操作(修改),可以使用读写锁。

读写锁的核心思想是将线程访问共同数据发出的请求分类:

  1. 读请求:只读,不修改共享数据
  2. 写请求: 存在修改共享数据的操作

当有多个读线程的时候,他们可以同时访问,但是写线程就必须要等他们读完才能访问,反过来也是,只不过写线程必须要一个一个来访问。读线程访问的时候,读写锁称之为读锁,写线程访问的时候,读写锁称之为写锁。

线程通信机制

线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺。

线程通信主要可以分为三种方式,分别为共享内存、消息传递和管道。每种方式有不同的方法来实现:

  1. 共享内存:多个线程共享同一块内存区域,通过读写共享内存来实现信息交流和数据共享。需要考虑线程安全问题,可以使用互斥锁、信号量等机制来保证数据的一致性。
  2. 消息传递:线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。wait/notify等待通知方式、join方式。
  3. 管道:通过管道来实现线程之间的通信。一个线程可以将数据写入管道,另一个线程可以从管道中读取数据。需要注意管道的大小限制和线程安全问题。

线程死锁

线程死锁指的是线程一直被阻塞的情况。比如:给线程加上互斥锁但是忘了解锁,那就会出现一直阻塞的情况。

避免死锁的建议:

  1. 使用互斥锁,信号量,条件变量,读写锁的时候
    • 占用互斥锁的进程要及时解锁
    • 通过sem_wait()函数占用信号量资源的线程,及时调用sem_post()函数进行释放
    • 当线程phtread_cond_wait()函数被阻塞时,一定要保证有其他线程唤醒此线程
    • 无论线程占用的是读锁还是写锁,都要及时解锁
  2. POSIX标准中,很多阻塞线程的函数都提供两个版本:tryxxx()、timexxx()。其中try是不会阻塞线程,time不会一直阻塞线程,多使用这两种可以大大降低死锁的概率。
  3. 多线程程序中,多个线程申请资源的顺序最好一致,比如线程1先申请matex1在申请matex2,而线程2先申请matex2,再申请matex1,就会发生顺序不一致导致的死锁。

协程

在多核场景下,如果是I/O密集型场景,就算开多个线程来处理,也未必能提升CPU的利用率,反而会增加线程切换的开销。另外,多线程之间假如存在临界区或者共享数据,那么同步的开销也是不可忽视的。

协程,正好可以解决上面的相关问题。

协程是一种用户态的轻量级线程。在一个用户线程上可以跑多个协程,这样就提高了单核的利用率。协程不像进程或者线程,可以让系统负责相关的调度工作,协程是处于一个线程中,系统是无感知的,所以想要在该线程中阻塞某个协程的话,就需要手工进行调度。

  1. 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。
  2. 一个线程可以拥有多个协程,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。
  3. 与其让操作系统调度,不如我自己来,这就是协程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

协程与线程主要区别是它将不再被内核调度,而是交给了程序自己,而线程是将自己交给内核调度,所以也不难理解golang中调度器的存在。

进程、线程、协程对比

进程与线程比较

关系:

  1. 一个进程内有一个或多个线程,某进程内的线程在其它进程不可见。
  2. 二者均可并发执行。

区别
3. 地址空间:进程有自己独立的地址空间,进程之间相互独立。线程是进程内的一个执行单元,一个进程内的所有线程共享地址空间。
4. 资源拥有:进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见。
5. 进程是资源分配的基本单位,线程是处理器调度的基本单位。
6. 调度和切换:线程上下文切换比进程上下文切换要快得多。

协程与线程比较

  1. 一个线程可以多个协程,一个进程也可以单独拥有多个协程。
  2. 线程进程都是同步机制,而协程则是异步。
  3. 程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。

如何选择进程、线程、协程?

  1. IO密集型一般使用多线程或者多进程。
  2. CPU密集型一般使用多进程。
  3. 强调非阻塞异步并发的一般都是使用协程,当然有时候也是需要多进程线程池结合的,或者是其他组合方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/506258.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Vue3】el-checkbox-group实现权限配置和应用

一. 需求 针对不同等级的用户,配置不同的可见项 配置效果如下 (1)新增,获取数据列表 (2)编辑,回显数据列表 应用效果如下 (1)父级配置 (2)子级…

【Selenium+python】自动化测试登录界面

前言:已经学习selenium许久了,奈何公司的项目还在码代码中...,感觉自己学的东西快忘的差不多了,所以就找个网站练练手,顺便回顾一下UI自动化的知识,也希望跟我一样的小白有所受益。 一、用例分析&#xff…

Benjamin Button‘sLetter to Daughter 英语阅读

Benjamin ButtonsLetter to Daughter 来源: The Curious Case of Benjamin Button 官方翻译 For what its worth: Its never too late, or in my case, too early to bewhoever you want to be. Theres no time limit. Start whenever you want. You can change or stay t…

向量点乘有哪些作用呢

如下: 1.找到两个向量之间的夹角(不用多说) 2.求一个向量投影在另一个向量的投影: 我们把图中b的在a上的投影向量称作b1吧,因为b1就在a上,所以只需要求出b1的大小,然后乘以a的单位向量,我们就得到向量b1了…

【LeetCode热题100】114. 二叉树展开为链表(二叉树)

一.题目要求 给你二叉树的根结点 root ,请你将它展开为一个单链表: 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。 …

KeepAlived使用介绍

目录 1、Introduce 2、基本使用 (1)安装 (2)配置文件 (3)使用教程 1、Introduce keepalived是一个用于实现高可用性和负载均衡的开源软件。它提供了一种轻量级的方式来管理多个服务器,并确保…

隐私计算实训营学习六:隐语PIR介绍及开发指南

文章目录 一、隐语实现的PIR总体介绍1.1 PIR的定义和种类1.2 隐语PIR功能分层 二、Index PIR-SealPIR介绍三、Keyword PIR- Labeled PSI介绍四、隐语PIR后续计划 一、隐语实现的PIR总体介绍 1.1 PIR的定义和种类 PIR(Private Information Retrieval PIR)隐匿查询:…

YOLOv8改进 | 低照度检测 | 2024最新改进CPA-Enhancer链式思考网络(适用低照度、图像去雾、雨天、雪天)

一、本文介绍 本文给大家带来的2024.3月份最新改进机制,由CPA-Enhancer: Chain-of-Thought Prompted Adaptive Enhancer for Object Detection under Unknown Degradations论文提出的CPA-Enhancer链式思考网络,CPA-Enhancer通过引入链式思考提示机制&am…

使用虚幻引擎为AR体验提供动力

Powering AR Experiences with Unreal Engine ​​​​​​​ 目录 1. 虚幻引擎概述 2. 虚幻引擎如何为AR体验提供动力 3. 虚幻引擎中AR体验的组成部分是什么? 4. 使用虚幻引擎创建AR体验 5. 虚幻引擎中AR的优化提示 6. 将互动性融入AR与虚幻引擎 7. 在AR中…

C++_Function包装器和bind

文章目录 前言第一种第二种 仿函数第三种 lambda表达式 一、Function包装器二、使用场景三、std::bind 前言 到目前为止的学习&#xff0c;我们知晓了三种方式来传函数。 第一种 #include<iostream>int Plus(int i, int j) {return i j; }int main() {int(*func)(int…

从大厂裸辞半年,我靠它成功赚到了第一桶金,如果你失业了,建议这样做,不然时间太久了就完了

程序员接私活和创业是许多技术从业者关注的话题。下面我将介绍一些程序员接私活和创业的渠道和建议&#xff1a; 接私活的渠道&#xff1a; 自媒体平台&#xff1a; 可以利用社交媒体、个人博客、技术社区等平台展示自己的作品和技能&#xff0c;吸引潜在客户。自由工作平台&…

竞赛课第五周(并查集+Treap树的应用)

目的&#xff1a; &#xff08;1&#xff09;熟悉并掌握并查集的应用 &#xff08;2&#xff09;熟悉并掌握BST &#xff08;3&#xff09;熟悉并掌握Treap树的建立与应用 实验内容&#xff1a; 1.并查集 poj 1611 嫌疑人 严重急性呼吸系统综合症 (SARS) 是一种病因不明的…

书生·浦语大模型-第一节课笔记

视频总结 23年发布的模型在一些材料中归位指令微调模型&#xff0c;后面逐渐升级应该已经是train的模型了 技术报告总结 InternLM2 Technical Report 评测与特点 6 dimensions and 30 benchmarks, long-context modeling, and open-ended subjective evaluations长文本…

智能革命:ChatGPT3.5与GPT4.0的融合,携手DALL·E 3和Midjourney开启艺术新纪元

迷图网(kk.zlrxjh.top)是一个融合了顶尖人工智能技术的多功能助手&#xff0c;集成了ChatGPT3.5、GPT4.0、DALLE 3和Midjourney等多种智能系统&#xff0c;为用户提供了丰富的体验。以下是对这些技术的概述&#xff1a; ChatGPT3.5是由OpenAI开发的一个自然语言处理模型&#x…

设计模式学习笔记 - 设计模式与范式 -行为型:2.观察者模式(下):实现一个异步非阻塞的EventBus框架

概述 《1.观察者模式&#xff08;上&#xff09;》我们学习了观察者模式的原理、实现、应用场景&#xff0c;重点节介绍了不同应用场景下&#xff0c;几种不同的实现方式&#xff0c;包括&#xff1a;同步阻塞、异步非阻塞、进程内、进程间的实现方式。 同步阻塞最经典的实现…

springboot配置文件application.properties,application.yml/application.yaml

application.properties Springboot提供的一种属性配置方式&#xff1a;application.properties 初始时配置文件中只有一行语句。 启动时&#xff0c;比如tomcat的端口号默认8080&#xff0c;路径默认。如果我们要改变端口号及路径之类的可以在application.properties中配置。…

基于微信小程序的自习室预约系统的设计与实现

基于微信小程序的自习室预约系统的设计与实现 文章目录 基于微信小程序的自习室预约系统的设计与实现1、前言介绍2、功能设计3、功能实现4、开发技术简介5、系统物理架构6、系统流程图7、库表设计8、关键代码9、源码获取10、 &#x1f389;写在最后 1、前言介绍 伴随着信息技术…

ESP8266 WiFi物联网智能插座—上位机软件实现

1、软件架构 上位机主要作为下位机数据上传服务端以及节点调试的控制端&#xff0c;可以等效认为是专属版本调试工具。针对智能插座协议&#xff0c;对于下位机进行可视化监测和管理。 软件技术架构如下&#xff0c;主要为针对 Windows 的PC 端应用程序&#xff0c;采用WPF以及…

pyqt 创建右键菜单栏

class MainModule(QMainWindow, Ui_MainWindow):def __init__(self):super().__init__(parentNone)self.setupUi(self)# 允许出现菜单栏self.tableWidget.setContextMenuPolicy(Qt.CustomContextMenu)# 对空间添加右键菜单栏处理 self.tableWidget.customContextMenuRequested.…

C练习题(1)

变种水仙花&#xff08;来自牛课网&#xff09; 题目 变种水仙花数 - Lily Number&#xff1a;把任意的数字&#xff0c;从中间拆分成两个数字&#xff0c;比如1461 可以拆分成&#xff08;1和461&#xff09;,&#xff08;14和61&#xff09;,&#xff08;146和1),如果所有拆…