程序,进程和线程有什么区别?
本文非原创,译自 www.backblaze.com/blog/whats-...
程序、进程和线程都是与软件执行相关术语,但你可能不清楚它们的真正含义。无论你是经验丰富的开发者、有抱负的爱好者,还是只是在打开电脑的任务管理器或Mac的活动监视器时想知道自己在看什么,学习这些术语对于理解计算机的工作方式至关重要。
什么是计算机程序?
什么是计算机程序? 程序是一系列编码指令,告诉计算机执行给定的任务。程序有很多种类型,包括内置在操作系统(OS)中的程序和用于完成特定任务的程序。一般来说,特定任务的程序被称为应用程序(或应用)。例如,你可能正在使用谷歌Chrome、Mozilla Firefox或苹果Safari等网页浏览器应用阅读这篇文章。其他常见的应用包括电子邮件客户端、文字处理器和游戏。
创建计算机程序的过程包括设计算法、用编程语言编写代码,然后编译或解释这些代码,将其转化为计算机可以执行的机器可读指令。
什么是编程语言?
编程语言是人类和计算机相互交流的方式。它们是一套规范化的规则和语法。
编译型与解释型程序
许多程序是用编译型语言编写的,并使用诸如C、C++、C#等编程语言创建。最终结果是一份代码的文本文件,它被编译成二进制形式以在计算机上运行(关于二进制形式的更多内容将在接下来的几段中介绍)。这个文本文件直接与你的计算机交流。尽管它们通常很快,但与解释型程序相比,它们也是固定的。这既有好处也有坏处:你可以更好地控制像内存管理这样的事情,但你依赖于平台,如果你需要在代码中更改某些内容,通常需要更长的时间来构建和测试。
还有另一种称为解释型程序的程序。它们需要一个额外的程序来接收你的程序指令,并将其翻译成计算机可以理解的代码。与编译型语言相比,这些类型的程序是平台独立的(你只需要找到一个不同的解释器,而不是编写一个全新的程序),并且它们通常占用的空间更小。一些最常见的解释型编程语言包括Python、PHP、JavaScript和Ruby。
最终,这两种类型的程序都以二进制形式运行并加载到内存中。程序必须以二进制运行,因为你的计算机CPU只能理解二进制指令。
什么是二进制代码?
二进制是计算机的基础语言。在最基本的层面上,计算机只使用两种电流状态------开和关。开状态用1表示,关状态用0表示。二进制与我们日常生活中使用的十进制不同。在十进制中,每个数字位置可以是从0到9的任何数字。在二进制系统中,每个位置要么是0,要么是1。
你可能听过一个关于程序员的笑话,"世界上只有10种人,懂二进制的人和不懂二进制的人"
程序通常以计算机可执行的形式存储在磁盘或非易失性存储器上。在此之前,它们使用如C、Lisp、Pascal或其他编程语言编写而成,这些语言包含逻辑、数据处理、设备管理、递归以及用户交互的指令。为了在计算机上运行,程序最终被编译成二进制形式(即由1和0组成)的代码文件。另一种类型的程序是"解释型语言",它们在运行时才被解释为可执行代码,而不是预先编译。常见的解释型语言包括Python、PHP、JavaScript和Ruby。
不管采用哪种方式,程序运行时都以二进制形式加载到内存中。因为计算机的CPU(中央处理单元)只理解二进制指令,所以当CPU运行程序时,程序需要转换成二进制形式。
二进制是计算机的原生语言,因为电子电路有两种基本状态:开或关,分别用0和1表示。我们平时使用的是基于10进制的通用编码系统,其中每个数位可以是从0到9的任意数字。而在二进制中,每个位置只能是0或1。
计算机程序如何存储和运行?
程序通常以可执行格式存储在磁盘或非易失性内存中。让我们详细解释一下这个过程。
在这个上下文中,我们会讨论计算机拥有两种类型的内存:易失性和非易失性。易失性内存是临时的,并实时处理。它更快、更容易访问,并提高了计算机的效率。然而,它不是永久的。当计算机关闭时,这种类型的内存会重置。
另一方面,非易失性内存除非被删除,否则是永久的。虽然访问速度较慢,但它可以存储更多信息。因此,它更适合用来存储程序。可执行格式的文件简单来说就是运行程序的文件。它可以由你的CPU(即处理器)直接运行。这些文件类型的例子包括Windows中的.exe和Mac中的.app。
程序运行需要哪些资源?
一旦程序以二进制形式加载到内存中,接下来会发生什么?
正在执行的程序需要从操作系统和内存中获取资源来运行。没有这些资源,你无法使用程序。幸运的是,你的操作系统自动管理分配资源给程序的工作。无论你使用的是Microsoft Windows、macOS、Linux、Android还是其他系统,你的操作系统总是在努力指导计算机的资源,以将你的程序转化为正在运行的过程。
除了操作系统和内存资源之外,每个程序都需要一些基本的资源。
- 寄存器。它是计算机处理器(CPU)内部的一种非常快速的存储设备,它包含进程可能需要的数据,如指令、存储地址或其他数据。
- 程序计数器。也称为指令指针,程序计数器扮演组织角色。它跟踪计算机在其程序序列中的位置。
- 栈。栈是一种数据结构,用于存储有关计算机程序活动子程序的信息。它被用作进程的临时空间。它与为进程动态分配的内存区别开来,后者称为"堆"。
什么是计算机进程?
当程序连同其运行所需的所有资源一起加载到内存中时,它被称为进程。你可能会有一个单一程序的多个实例。在这种情况下,该运行程序的每个实例都是一个进程。
每个进程都有一个独立的内存地址空间。这个独立的内存地址很有用,因为它意味着进程独立运行,并与其他进程隔离。然而,进程不能直接访问其他进程中的共享数据。从一个进程切换到另一个进程需要一些时间(相对而言)来保存和加载寄存器、内存映射和其他资源。
拥有独立进程对用户来说很重要,因为这意味着一个进程不会损坏或破坏其他进程。如果一个单独的进程出现问题,你可以关闭那个程序并继续使用你的计算机。实际上,这意味着你可以结束一个功能失常的程序,并以最小的中断继续工作。
什么是线程?
最后一个问题是线程。线程是进程内的执行单元。一个进程可能只有一个线程,也可能有多个线程。
进程启动时,会收到内存和其他计算资源的分配。进程中的每个线程共享这些内存和资源。对于单线程进程,进程只包含一个线程。
在多线程进程中,进程包含多个线程,同时(更准确地说是"虚拟地"同时------你可以在下面关于并发的部分阅读更多相关内容)完成多项任务。
前面我们讨论了栈和堆,这是线程或进程可用的两种内存类型。区分这些内存类型很重要,因为每个线程都将拥有自己的栈。然而,一个进程中的所有线程将共享堆。
有些人将线程称为轻量级进程,因为它们有自己的栈,但可以访问共享数据。由于线程与进程和进程内的其他线程共享相同的地址空间,线程间的通信变得容易。缺点是,进程中一个功能失常的线程可能影响整个进程的可行性。
线程和进程的工作步骤
以下是你在计算机上打开应用程序时发生的事情:
- 程序最初是编程代码的文本文件。
- 程序被编译或解释为二进制形式。
- 程序被加载到内存中。
- 程序变成一个或多个正在运行的进程。进程通常相互独立。
- 线程作为进程的子集存在。
- 线程之间的通信比进程之间更容易。
- 线程更容易受到同一进程中其他线程引起的问题的影响。
对比 | 进程 | 线程 |
---|---|---|
定义 | 拥有自己内存空间的独立程序。 | 进程的轻量、较小单位,共享内存。 |
创建开销 | 由于有独立内存空间,开销较高。 | 由于共享相同的内存空间,开销较低。 |
隔离性 | 进程彼此隔离。 | 线程共享相同的内存空间。 |
资源分配 | 每个进程都有自己的系统资源集。 | 线程在同一进程中共享资源。 |
独立性 | 进程相互之间更加独立。 | 线程在进程内相互依赖。 |
故障影响 | 一个进程的故障不会直接影响其他进程。 | 一个线程的故障可能影响同一进程中的其他线程。 |
同步需求 | 由于进程是隔离的,同步需求较少。 | 由于资源共享,需要仔细同步。 |
示例用途 | 运行多个独立应用程序。 | 在单个应用程序内进行多线程处理,以实现并行性。 |
内存使用 | 通常消耗更多内存。 | 与进程相比,消耗较少的内存。 |
关于并发和并行
你可能会问,进程或线程是否可以同时运行。答案是:这取决于具体情况。在拥有多个处理器或CPU核心的环境中,同时执行多个进程或线程是可行的。然而,在单处理器系统上,真正的同时执行是不可能的。在这些情况下,会采用进程调度算法来在运行的进程或线程之间共享CPU,从而创造出并行执行的假象。每个任务被分配一个"时间片",任务之间的快速切换通常对用户来说是不可察觉的,看起来就像是同时发生的。术语"并行性"(指真正的同时执行)和"并发性"(指随时间交错进行的进程以模拟同时执行)区分了这两种操作模式,无论是真正的同时进行还是近似模拟。
- 并发(Concurrency) :在单处理器或多处理器系统中,通过进程或线程的交错执行(即它们在不同时间点运行),来模拟多任务同时进行。在单处理器系统中,由于只能在任一时刻执行一个任务,因此并发是实现多任务处理的唯一方式。 并行(Parallelism) :在多处理器系统中,不同的进程或线程可以在相同的时间真正同时执行。这利用了多核或多处理器的硬件优势,以实现更高效的任务处理。
Google Chrome 如何使用进程和线程
为了说明进程和线程的影响,我们来考虑一个实际例子:许多人使用的程序------Google Chrome 浏览器。
在设计 Chrome 浏览器时,Google 面临了几个重要的决策。例如,Chrome 应该如何处理在使用浏览器时经常同时发生的许多不同任务的问题?每个浏览器窗口(或标签页)可能会与互联网上的多个服务器通信,下载音频、视频、文本和其他资源。此外,许多用户大多数时间都会打开 10 到 20 个(或更多)浏览器标签页,每个标签页可能执行多个任务。
Google 必须决定如何处理所有这些任务。他们选择将 Chrome 中的每个浏览器窗口作为一个独立的进程运行,而不是作为一个或多个线程。这种方法带来了几个好处。
- 将每个窗口作为一个进程运行,可以保护整个应用程序免受程序错误和故障的影响。
- 将 JavaScript 程序隔离在一个进程中,可以防止它占用过多的 CPU 时间和内存,导致整个浏览器变得无响应。
话虽如此,Google 的设计决策也有权衡成本。为每个浏览器窗口启动一个新进程,与使用线程相比,在内存和资源上有更高的固定成本。他们赌的是,他们的方法最终会导致整体上的内存消耗较少。
在内存较低时,使用进程而不是线程可以提供更好的内存使用效率。实际上,一个不活跃的浏览器窗口被视为较低优先级。这意味着当需要内存用于其他进程时,操作系统可能会将其交换到磁盘。如果窗口是线程化的,就更难有效分配内存,最终导致计算机性能下降。
想要了解更多关于 Google 对 Chrome 的设计决策,可以查阅 Chromium 博客或 Chrome 介绍漫画。
下面的屏幕截图显示了在一台打开了许多标签页的 MacBook Air 上运行的 Google Chrome 进程。你可以看到,一些 Chrome 进程正在使用相当多的 CPU 时间和资源(例如,顶部的一个正在使用 44 个线程),而其他一些则使用较少。
Mac 上的"活动监视器"(或 Windows 中的"任务管理器")可以成为优化计算机性能或排查问题的有价值助手。如果你的计算机运行缓慢,或者某个程序或浏览器窗口长时间无响应,你可以使用系统监视器检查其状态。
在某些情况下,你可能会看到某个进程被标记为"无响应"。尝试退出那个进程,看看你的系统是否运行得更好。如果某个应用程序占用了大量内存,你可能会考虑选择另一个能完成相同任务的应用程序。