什么是进程和线程?

54

是的,我已经阅读了许多与操作系统相关的资料,并且还在继续阅读。但似乎所有的材料都是以“抽象”的方式描述进程和线程,这使得它们对行为和逻辑组织进行了许多高层次的阐述。我想知道它们在“物理上”是什么? 我认为,它们只是由内核代码维护和使用的一些“内存数据结构”,以促进程序的执行。例如,操作系统使用一些进程数据结构(PCB)来描述分配给某个程序的进程的各个方面,例如其优先级、地址空间等。这样可以吗?


9
只是为了增加乐趣 - 在Windows下也有“纤程(fibers)”哦。 ;) - Vilx-
2
在Windows 7 x64中,存在用户模式线程。 - Ana Betts
进程和线程几乎是相同的,除了进程不共享任何内存区域,而线程则共享堆...所以线程是共享堆的进程。 - vtd-xml-author
2
@Jimmy:不,进程不能运行任何东西。线程才能让事情发生。线程有调用堆栈和寄存器。进程有内核对象、内存和一组线程。 - Macke
@Marcus:一个进程通常包含一个单独的线程,它可以生成其他进程或线程...所以当你将进程与其默认线程合并时,进程也有调用堆栈和寄存器,线程也有内核对象,从内核的角度来看,它们的表示方式与进程基本相同(就调度程序而言,它们通常是无法区分的,就我对Linux的理解而言...) - vtd-xml-author
@Jimmy:好的,从Linux的角度来看这是有道理的,但在其他操作系统中可能并非如此。 - Macke
22个回答

38

了解进程和线程之间的区别的首要是事实是,进程不运行,而线程运行

那么,什么是线程?最接近的解释是一个执行状态,也就是:CPU寄存器、栈等的组合。您可以在任何给定时刻中断调试器来证明这一点。您看到了什么?调用堆栈、一组寄存器。这就是线程。

现在,什么是进程。嗯,它就像是运行线程的抽象“容器”实体。就操作系统而言,在第一次近似中,它是一个OS为其分配了一些VM,并分配了一些系统资源(如文件句柄、网络套接字)的实体。

它们如何协同工作呢?操作系统通过为其保留一些资源并启动一个“主”线程来创建“进程”。然后该线程可以生成更多的线程。这些是一个进程中的线程。它们或多或少可以以某种方式共享这些资源(例如,锁定可能需要避免它们破坏其他人的乐趣等)。从那里开始,操作系统通常负责维护那些线程在那个VM“内部”(检测和防止尝试访问不属于该进程的内存),提供某种类型的调度这些线程,以便它们可以“一个接一个地运行而不仅仅是一直运行一个”。


5
+1. 很好的解释...希望能获得更多的赞,以超越其他(尝试的)解释。 - Macke
那么,ProcessA中的线程可以启动一个新的ProcessB吗?如果可以,如何实现? - Aaron B.

32
通常情况下,当您运行像notepad.exe这样的可执行文件时,会创建一个进程。这些进程可以生成其他进程,但在大多数情况下,每个可执行文件只有一个进程。在进程内部,可以有许多线程。通常开始时有一个线程,它通常从程序的“入口点”即main函数开始。指令按顺序一次执行一个,就像一个只有一只手的人,线程在进行下一个操作之前只能一次执行一个操作。
第一个线程可以创建其他线程。每个附加线程都有自己的入口点,通常定义为一个函数。该进程就像是容纳了其内部生成的所有线程的容器。
这是一个相当简单的解释。我可以提供更详细的信息,但可能会与您在教科书中找到的重叠。
编辑:您会注意到我的解释中有很多“通常”的说法,因为偶尔会有一些做事情方式截然不同的稀有程序。

7
在我看来,计算机内部有很多与“第一因素”相关的东西,就像宇宙大爆炸一样…… - smwikipedia
有一件事,OP 询问了线程和进程的物理组织方式... - Bruno Brant
@Bruno,我想我在物理上的解释可能有所不同,因为在物理世界中,进程和线程只是在某些金属和硅中旋转的电子。 - AaronLS
@aaronls:你可以说每个结构体占用的内存单元及其关系是一种物理关系.. :) - Macke
@aaronls: 我希望当你为数据库创建物理数据模型时,不要真正指定某些电子的自旋……否则你就是在对数据库管理员刻薄了 :) - Bruno Brant

11

线程和进程难以进行非抽象化的描述的原因之一是它们本身就是抽象的。

它们具体的实现方式差异极大。

例如,比较 Erlang 进程和 Windows 进程:Erlang 进程非常轻量级,通常不到 400 字节。在不太新的笔记本上,您可以启动 1000 万个进程而没有任何问题。它们启动非常快,死亡也非常快,并且您应该能够将它们用于非常短的任务。每个 Erlang 进程都有其自己的垃圾回收器。Erlang 进程永远无法共享内存。

Windows 进程非常重,有时高达数百兆字节。如果您很幸运,可以在强大的服务器上启动几千个进程。它们的启动和死亡速度相对较慢。Windows 进程是 IDE、文本编辑器或文字处理器等应用程序的单元,因此它们通常需要存在相当长的时间(至少几分钟)。它们有自己的地址空间,但没有垃圾回收器。Windows 进程可以共享内存,尽管默认情况下它们不会。

线程也是类似的问题:在 x86 上的 NPTL Linux 线程最小可以只有 4 KiB,通过一些技巧,您可以在 32 位 x86 计算机上启动超过 800000 个线程。使用数千个甚至数万个线程后,计算机肯定是可用的。.NET CLR 线程的最小大小约为 1 MiB,这意味着只要启动 4000 个就会耗尽您在 32 位计算机上的整个地址空间。因此,虽然通常情况下 4000 个 NPTL Linux 线程不是问题,但在 .NET CLR 中,您甚至无法启动 4000 个线程,因为在那之前你的内存就已经耗尽了。

操作系统进程和操作系统线程的实现在不同的操作系统之间也非常不同。主要有两种方法:内核仅知道进程,线程由用户空间库实现,完全不涉及内核。在这种情况下,又有两种方法:1:1(每个线程映射到一个内核进程)或m:n(m个线程映射到n个进程,其中通常m>n,并且经常n == #CPU)。这是许多操作系统在线程被发明后采取的早期方法。然而,它通常被认为效率低下,并且几乎所有系统都已将其替换为第二种方法:线程在内核中实现(至少部分实现),因此内核现在了解两个不同的实体,即线程和进程。

一种采用第三种方法的操作系统是Linux。在Linux中,线程既不在用户空间中实现,也不在内核中实现。相反,内核提供了一个抽象,既可以表示线程也可以表示进程(以及更多其他内容),称为任务(Task)。任务是内核调度实体,它携带一组标志,这些标志确定它与其兄弟共享哪些资源,哪些是私有的。

根据你设置的标志,你可以得到一个线程(共享几乎所有内容)或一个进程(共享系统资源,如系统时钟、文件系统命名空间、网络命名空间、用户ID命名空间、进程ID命名空间,但是不共享地址空间)。但是你还可以得到其他非常有趣的东西。你可以轻松地获得类似于BSD样式的jails(基本上与进程相同的标志,但是不共享文件系统或网络命名空间)。或者你可以得到其他操作系统称为虚拟化容器或区域的东西(像一个jail,但不共享UID和PID命名空间和系统时钟)。几年前通过一种叫做KVM(内核虚拟机)的技术,你甚至可以获得一个完整的虚拟机(什么都不共享,甚至不共享处理器的页表)。[这个很酷的事情是,你可以重用内核中高度调优成熟的任务调度程序来处理所有这些东西。Xen虚拟机经常被批评其调度程序性能较差。KVM开发人员有比Xen更优秀的调度程序,最好的事情是他们甚至不需要编写任何代码!]

所以,在Linux上,线程和进程的性能比在Windows和许多其他系统上更接近,因为在Linux上,它们实际上是相同的东西。这意味着使用模式非常不同:在Windows上,你通常基于它们的权重来决定使用线程还是进程:我能负担得起一个进程吗?或者我应该使用线程,即使我实际上不想共享状态?在Linux(通常也适用于Unix),你根据它们的语义来决定:我是否真的想共享状态?

进程在Unix上比Windows上轻量级的原因之一是不同的使用方式:在Unix上,进程既是并发性的基本单元,也是功能的基本单元。如果你想使用并发性,你使用多个进程。如果你的应用程序可以分解成多个独立的部分,你使用多个进程。每个进程只做一件事情,而且仅限于那一件事情。即使是简单的一行Shell脚本通常也涉及数十个或数百个进程。应用程序通常由许多经常是短暂存在的进程组成。

在Windows操作系统中,线程是并发的基本单位,而COM组件或.NET对象是功能的基本单位。应用程序通常由一个长时间运行的进程组成。

需要注意的是,它们用于非常不同的目的,并具有非常不同的设计目标。并不是说其中一个比另一个更好或更差,只是它们非常不同,因此只能以非常抽象的方式描述共同特征。

关于线程和进程,几乎唯一可以说的几件事情就是:

  • 线程属于进程
  • 线程比进程轻量级
  • 线程彼此之间共享大部分状态
  • 进程之间共享的状态要比线程少得多(特别是,除非明确请求,否则它们通常不会共享任何内存)

实际上,Linux 上的进程和线程在“负载”方面与 Windows 上几乎没有什么区别,几乎无法区分。 - Ana Betts
@Paul Betts:是的,谢谢。我应该提一下这个。(实际上,在Linux上,进程和线程之间几乎没有什么区别,它们都只是使用不同标志创建的任务。) - Jörg W Mittag
+1 用于区分进程、线程和任务。 - Jay D

7

我认为:

一个进程具有内存空间、打开的文件等,并且可能有一个或多个线程。

线程是一个指令流,可以由系统在处理器上进行调度。


2
这并不是很正确,特别是你对线程的描述。 - Ana Betts
有趣的是,我认为我对进程的定义有点薄弱,但我对线程的定义很好。请告诉我哪里不太正确。 - Ben
1
一个线程是一个调用堆栈、一个寄存器集(数据)和一个线程本地变量的内存区域。一个核心可能正在运行它,也可能没有(如果没有,则寄存器内容会被推到内存中)。然后进行一些特定于操作系统的簿记,如动态优先级、指向进程的指针等等。 - Macke

7
请看我之前在SO上详细回答的内容。它提供了一个玩具内核结构的洞察,负责维护进程和线程...希望这能帮到你。 最好的问候, 汤姆。

@Paul:谢谢夸奖!令人惊讶的是,它没有得到足够的投票来证明它是一个简洁解释抽象视图的好答案! :) - t0mm13b

5

5
一个进程是执行程序时使用的一组资源的容器。
进程包括以下内容:
- 私有虚拟地址空间 - 一个程序。 - 一个句柄列表。 - 一个访问令牌。 - 一个唯一的进程 ID。 - 至少一个线程。 - 指向父进程的指针,无论该进程是否仍存在。
话虽如此,一个进程可以包含多个线程。
进程本身可以分组为作业,作业是进程的容器,并作为单个单元执行。
一个线程是Windows用来调度在CPU上执行指令的东西。每个进程至少有一个。
我在我的维基上有几页你可以看看: 进程 线程

1
“资源”是处理的一个好关键字。操作系统的目标是使在进程内运行的线程之间共享资源变得更加高效。 - rwong

3

线程是操作系统调度程序中的内存结构,正如您所说。线程指向内存中某些指令的起始位置,并在调度程序决定它们应该被处理时进行处理。当线程正在执行时,硬件计时器将运行。一旦达到所需时间,将调用中断。在此之后,硬件将停止当前程序的执行,并调用已注册的中断处理程序函数(该函数将是调度程序的一部分),以通知当前线程已完成执行。


3

物理上:

  • 进程是一个结构体,维护了拥有的凭证、线程列表和打开的句柄列表。

  • 线程是一个包含上下文(即保存的寄存器集合和要执行的位置)、描述哪些页面映射到进程虚拟地址空间的一组PTEs以及所有者的结构体。

当然,这只是一个极其简化的解释,但它涵盖了重要的部分。Linux和Windows上的基本执行单元都是线程 - 内核调度程序并不关心进程(非常少关注)。这就是为什么在Linux上,一个线程只是一个与另一个进程共享PTEs的进程。


除了寄存器集和执行指针之外,如果允许函数调用,每个线程还需要拥有自己的堆栈 - rwong

2

进程是由操作系统管理的在内存中运行应用程序的区域。线程是进程内的一个小区域,用于运行专门的任务。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接