草庐IT

线程与进程,你真得理解了吗

云深i不知处 2023-04-14 原文

线程与进程,你真得理解了吗

相信大家面试时一定没少被一个问题刁难,那就是进程和线程的区别是什么?这个问题延申开来并不像表面那么简单,今天就来深入一探。

开始前先看一组非常传神的图例,相信可以帮助你更好理解进程与线程的概念:

1 进程与线程的关系和区别

什么是进程

先给一个定义进程是一个具有一定独立功能的程序在一个数据集合上依次动态执行的过程。进程是一个正在执行的程序的实例,包括程序计数器、寄存器和程序变量的当前值。

进程有哪些特征?

  1. 进程依赖于程序运行而存在,进程是动态的,程序是静态的;
  2. 进程是操作系统进行资源分配和调度的一个独立单位(CPU除外,线程是处理器任务调度和执行的基本单位);
  3. 每个进程拥有独立的地址空间,地址空间包括代码区、数据区和堆栈区,进程之间的地址空间是隔离的,互不影响。

什么是线程?

进程的创建、销毁与切换存在着较大的时空开销,因此人们急需一种轻型的进程技术来减少开销。在80年代,线程的概念开始出现,线程被设计成进程的一个执行路径,同一个进程中的线程共享进程的资源,因此系统对线程的调度所需的成本远远小于进程。

进程与线程的区别总结:

  • 本质区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。

  • 包含关系:一个进程至少有一个线程,线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

  • 资源开销:每个进程都有独立的地址空间,进程之间的切换会有较大的开销;线程可以看做轻量级的进程,同一个进程内的线程共享进程的地址空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。

  • 影响关系:一个进程崩溃后,在保护模式下其他进程不会被影响,但是一个线程崩溃可能导致整个进程被操作系统杀掉,所以多进程要比多线程健壮。

进程与线程的一个简陋模型用图表示的话,是这样的:


理论过于抽象难解,下面还是用大家喜闻乐见的现实中的例子去类比,没错还是工厂的例子:

在计算机这个大工厂中,进程被比作一个车间,为生产活动提供了设计图、场地、生产线(线程)等生产要素,而线程是这个车间中的一条条生产线。生产线本身会有一个操作台,具体的零件在这里被生产。生产线必须由工人操作才能动起来,当工人来到一个生产线旁并启动它之前,必须查阅生产线的生产记录以便弄清楚这个生产线的零件加工到哪种程度了,然后才能准确地接续生产,当工人停止生产线前也必须记录这次的生产进度以备下次读取,这些进度信息可以理解为上下文,读取和记录生产进度的过程称为上下文切换

一个工人可以在多条生产线间穿梭操作,就像CPU在不同线程间切换一样,这个动作被称为并发,与之对应的,多个工人操作多条生产线同时生产,称为并行。如果生产线不需要太多原料输入就能生产,那这种生产任务被称作CPU密集型,反之如果生产线大部分时间在等待原料的输入,那这种任务被称为IO密集型。显然,前者最好一条生产线由一个工人专管效率更高,而后一种任务,一个人在原料输入的间隙去操作其他生产线,无疑能提高工人利用率。

2 并行与并发

上面的例子提到了并发与并行,这里更精准地阐述一下。

一个基本的事实前提一个CPU在一个瞬间只能处理一个任务。但为什么在我们人类视角,哪怕是单核心计算机也能同时做很多事情,比如同时听音乐和浏览网页,作为整个系统唯一可以完成计算任务的 CPU 是如何保证两个进程“同时进行”的呢?时间片轮转调度!

每个进程会被操作系统分配一个时间片,即每次被 CPU 选中来执行当前进程所用的时间。时间一到,无论进程是否运行结束,操作系统都会强制将 CPU 这个资源转到另一个进程去执行。为什么要这样做呢?因为只有一个单核 CPU,假如没有这种轮转调度机制,那它该去处理写文档的进程还是该去处理听音乐的进程?无论执行哪个进程,另一个进程肯定是不被执行,程序自然就是无运行的状态。如果 CPU 一会儿处理 word 进程一会儿处理听音乐的进程,起初看起来好像会觉得两个进程都很卡,但是 CPU 的执行速度已经快到让人们感觉不到这种切换的顿挫感,就真的好像两个进程在“并行运行”。

随着多核心CPU的出现,真正的并行得以实现,于是并行与并发的区别也成了面试常见题:

所谓的进程上下文,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈上的内容,当内核需要切换到另一个进程时,它 需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。

3 线程共享了进程哪些资源

这一节选自 知乎-码农的荒岛求生,文章非常精彩。

“进程是操作系统分配资源的单位,线程是调度的基本单位,线程之间共享进程资源”。

可是你真的理解了上面的那句话吗?到底线程之间共享了哪些进程资源,共享资源意味着什么?共享资源这种机制是如何实现的?

如果你没有答案的话,这篇文章就是为你准备的。

线程私有资源

函数运行时的信息保存在栈帧中,栈帧中保存了函数的返回值、调用其它函数的参数、该函数使用的局部变量以及该函数使用的寄存器信息,如图所示,假设函数A调用函数B:

此外,CPU 执行指令的信息保存在一个叫做程序计数器的寄存器中,通过这个寄存器我们就知道接下来要执行哪一条指令。由于操作系统随时可以暂停线程的运行,因此我们保存以及恢复程序计数器中的值就能知道线程是从哪里暂停的以及该从哪里继续运行了。

由于线程运行的本质就是函数运行,函数运行时信息是保存在栈帧中的,因此每个线程都有自己独立的、私有的栈区

同时函数运行时需要额外的寄存器来保存一些信息,像部分局部变量之类。这些寄存器也是线程私有的,一个线程不可能访问到另一个线程的这类寄存器信息。

从上面的讨论中我们知道,到目前为止,所属线程的栈区、程序计数器、栈指针以及函数运行使用的寄存器是线程私有的

以上这些信息有一个统一的名字,就是线程上下文,thread context。

我们也说过操作系统调度线程需要随时中断线程的运行并且需要线程被暂停后可以继续运行,操作系统之所以能实现这一点,依靠的就是线程上下文信息。

现在你应该知道哪些是线程私有的了吧。除此之外,剩下的都是线程间共享资源。那么剩下的还有什么呢?还有图中的这些。

这其实就是进程地址空间的样子,也就是说线程共享进程地址空间中除线程上下文信息中的所有内容,意思就是说线程可以直接读取这些内容。

接下来我们分别来看一下这些区域。

代码区

进程地址空间中的代码区,这里保存的是什么呢?从名字中有的同学可能已经猜到了,没错,这里保存的就是我们写的代码,更准确的是编译后的可执行机器指令。

那么这些机器指令又是从哪里来的呢?答案是从可执行文件中加载到内存的,可执行程序中的代码区就是用来初始化进程地址空间中的代码区的。


线程之间共享代码区,这就意味着程序中的任何一个函数都可以放到线程中去执行,不存在某个函数只能被特定线程执行的情况。

数据区

进程地址空间中的数据区,这里存放的就是所谓的全局变量。

什么是全局变量?所谓全局变量就是那些你定义在函数之外的变量,在 C 语言中就像这样:

堆区

堆区是程序员比较熟悉的,我们在 C/C++中用 malloc 或者 new 出来的数据就存放在这个区域,很显然,只要知道变量的地址,也就是指针,任何一个线程都可以访问指针指向的数据,因此堆区也是线程共享的属于进程的资源。

有关线程与进程,你真得理解了吗的更多相关文章

  1. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  2. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  3. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  4. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  5. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  6. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  7. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  8. ruby - 无法在 Ruby 中将 ffmpeg 作为子进程运行 - 2

    我正在尝试使用以下代码通过将ffmpeg实用程序作为子进程运行并获取其输出并解析它来确定视频分辨率:IO.popen'ffmpeg-i'+path_to_filedo|ffmpegIO|#myparsegoeshereend...但是ffmpeg输出仍然连接到标准输出并且ffmepgIO.readlines是空的。ffmpeg实用程序是否需要一些特殊处理?或者还有其他方法可以获得ffmpeg输出吗?我在WinXP和FedoraLinux下测试了这段代码-结果是一样的。 最佳答案 要跟进mouviciel的评论,您需要使用类似pope

  9. Ruby 守护进程导致 ActiveRecord 记录器 IOError - 2

    我目前正在用Ruby编写一个项目,它使用ActiveRecordgem进行数据库交互,我正在尝试使用ActiveRecord::Base.logger记录所有数据库事件具有以下代码的属性ActiveRecord::Base.logger=Logger.new(File.open('logs/database.log','a'))这适用于迁移等(出于某种原因似乎需要启用日志记录,因为它在禁用时会出现NilClass错误)但是当我尝试运行包含调用ActiveRecord对象的线程守护程序的项目时脚本失败并出现以下错误/System/Library/Frameworks/Ruby.frame

  10. ruby - 易于初学者理解的 Ruby 库 - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭3年前。Improvethisquestion我正处于学习Ruby的阶段,我想查看一些小型库的源代码以了解它们是如何构建的。我不知道什么是小型图书馆,但希望SO能推荐一些易于理解的图书馆来学习。因此,如果有人知道一两个非常小的库,这是新手Rubyists学习的好例子,请推荐!我想使用Manveru'sInnatelib,因为它试图保持在2000LOC以下,但我还不熟悉其中经常使用的Ruby速记。也许大约100-5

随机推荐