线程之间共享哪些资源?

最近,我在一次采访中被问到一个问题:进程和线程有什么区别?真的,我不知道答案。我想了一会儿,给出了一个非常奇怪的答案。

线程共享相同的内存,而进程不共享。回答完这个问题后,面试官对我邪恶地笑了笑,然后接连问了我几个问题:

问:你知道一个节目被分割的片段吗?

我的答案:是的(认为这是一个简单的)堆栈,数据,代码,堆

问:那么,告诉我:线程共享哪些片段?

我无法回答这个问题,最后只能把它们都说了出来。

请问,谁能就进程和线程之间的区别给出正确的、令人印象深刻的答案?

215385 次浏览

线程共享代码、数据段和堆,但不共享堆栈。

你是非常正确的,但是线程共享堆栈中的所有段除了。线程有独立的调用堆栈,但是其他线程堆栈中的内存仍然是可访问的,理论上你可以在其他线程的本地堆栈框架中保存内存指针(尽管你可能应该找到一个更好的地方来放置内存!)。

线程共享堆(有一个关于线程特定堆的研究),但当前的实现共享堆。(当然还有代码)

线程共享数据和代码,而进程不共享。栈不是为两者共享的。

进程也可以共享内存,更精确的代码,例如在Fork()之后,但这是一个实现细节和(操作系统)优化。由多个进程共享的代码将(希望)在第一次写入代码时变得重复——这被称为即写即拷。我不确定线程代码的确切语义,但我假设是共享代码。

Process   Thread


Stack   private   private
Data    private   shared
Code    private1  shared2

1代码是在逻辑上私有的,但出于性能原因可能被共享。 我不是百分之百的确定。

来自维基百科(我认为这对面试官来说是一个很好的回答:P)

线程不同于传统线程 多任务操作系统

  • 进程通常是独立的,而线程作为a . b0的子集存在 李过程< / >
  • 进程携带大量的状态信息,而多线程 在进程中也共享状态 作为内存和其他资源
  • 进程有单独的地址空间,而线程共享它们的地址空间 李地址空间< / >
  • 进程仅通过系统提供的进程间进行交互 李通信机制。< / >
  • 同一进程中线程之间的上下文切换通常更快 而不是上下文切换 李过程。< / >

线程共享一切[1]。整个进程只有一个地址空间。

每个线程都有自己的堆栈和寄存器,但所有线程的堆栈都在共享地址空间中可见。

如果一个线程在它的堆栈上分配了某个对象,并将该地址发送给另一个线程,它们对该对象的访问权是相等的。


实际上,我只是注意到一个更广泛的问题:我认为你混淆了这个词的两种用法。

可执行文件(如ELF)的文件格式有不同的部分,可以称为段,包含编译的代码(文本)、初始化的数据、链接器符号、调试信息等。这里没有堆段或堆栈段,因为它们是仅运行时结构。

这些二进制文件段可以分别映射到进程地址空间,具有不同的权限(例如,对于代码/文本,只读可执行;对于初始化的数据,写时复制不可执行)。

根据约定(由语言运行库强制执行),这个地址空间的区域用于不同的目的,如堆分配和线程堆栈。不过,这些都只是内存,并且可能没有分段,除非您在虚拟8086模式下运行。每个线程的堆栈是在线程创建时分配的内存块,当前堆栈顶部地址存储在堆栈指针寄存器中,每个线程保留自己的堆栈指针和其他寄存器。


好的,我知道:信号掩码,TSS/TSD等。地址空间,包括它所有映射的程序段,仍然是共享的。

告诉面试官这完全取决于操作系统的实现。

以Windows x86为例。只有2段[1],代码和数据。它们都被映射到整个2GB(线性,用户)地址空间。基础= 0,限制= 2 gb。他们本来可以做一个,但x86不允许一个段同时读/写和执行。所以他们做了两个,并设置CS指向代码描述符,其余(DS, ES, SS等)指向另一个[2]。但两者都指向同样的东西!

面试你的人做了一个隐藏的假设,他/她没有说出来,这是一个愚蠢的伎俩。

所以关于

< p >。告诉我是哪段线 分享吗?< / p >

细分市场与问题无关,至少在Windows上是这样。线程共享整个地址空间。只有一个堆栈段SS,它指向DS ES CS做的完全一样的东西[2]。即整个该死的用户空间。0-2GB。当然,这并不意味着线程只有一个堆栈。当然,每个都有自己的堆栈,但x86段并不用于此目的。

也许*nix做一些不同的事情。谁知道呢。这个问题的前提被打破了。


  1. 至少对于用户空间是这样的。
  2. ntsd notepad: cs=001b ss=0023 ds=0023 es=0023
在x86框架中,可以划分任意多的段(最多2^16-1)。ASM指令SEGMENT/ENDS允许这样做,操作符SEG和OFFSET允许初始化段寄存器。CS:IP通常由加载器初始化,但对于DS, ES, SS,应用程序负责初始化。 许多环境允许所谓的“简化段定义”,如.code, .data, .bss, .stack等,根据“内存模型”(小,大,紧凑等),加载器相应地初始化段寄存器。通常。data, .bss, .stack和其他常见的段(我已经20年没有这样做了,所以我不记得所有的)被分组在一个组中-这就是为什么通常DS, ES和SS指向同一个区域,但这只是为了简化事情 一般来说,所有段寄存器在运行时都可以有不同的值。 所以,面试的问题是正确的:CODE、DATA和STACK中的哪一个在线程之间共享。堆管理是另一回事——它只是对操作系统的一系列调用。但是如果你根本没有操作系统,比如在嵌入式系统中,你还能在你的代码中新建/删除吗?< / p >

我给年轻人的建议是——读一些好的汇编编程书。似乎大学的课程在这方面相当贫乏。

通常,线程被称为轻量级进程。如果我们把内存分成三个部分,那么它将是:代码,数据和堆栈。 每个进程都有自己的代码、数据和堆栈部分,由于这种上下文切换时间有点高。为了减少上下文切换时间,人们提出了线程的概念,线程与其他线程/进程共享数据段和代码段,并拥有自己的STACK段

一个进程有代码段、数据段、堆段和堆栈段。现在,一个或多个线程的指令指针(IP)指向进程的代码段。数据段和堆段由所有线程共享。那么堆栈区域呢?堆栈区域到底是什么?它是进程创建的一个区域,仅供其线程使用…因为使用堆栈比使用堆要快得多。进程的堆栈区域在线程之间划分,即如果有3个线程,那么进程的堆栈区域被划分为3个部分,每个部分分配给3个线程。换句话说,当我们说每个线程都有自己的堆栈时,该堆栈实际上是分配给每个线程的进程堆栈区域的一部分。当线程完成执行时,线程的堆栈将被进程回收。事实上,不仅进程的堆栈被分配给线程,而且线程使用的所有寄存器集(如SP、PC和状态寄存器)都是进程的寄存器。 因此,当涉及到共享时,代码、数据和堆区域是共享的,而堆栈区域只是在线程之间划分

真正需要指出的是,这个问题有两个方面——理论方面和实现方面。

首先,让我们看看理论方面。您需要从概念上理解进程是什么,才能理解进程和线程之间的区别以及它们之间共享的内容。

我们从Tanenbaum的现代操作系统3e中的2.2.2经典线程模型节得到以下内容:

流程模型基于两个独立的概念:资源 分组和执行。有时把它们分开是有用的; 这就是线程....的由来< / p >

他继续说:

看待一个过程的一种方式是,它是一种实现 将相关资源分组。进程有一个地址空间 包含程序文本和数据,以及其他资源。这些 资源可能包括打开的文件、子进程、挂起的告警、 信号处理程序、会计信息等。通过把它们 以流程的形式组合在一起,可以更容易地管理它们。 进程的另一个概念通常是执行线程 缩短到只有线。线程有一个程序计数器 跟踪下一个要执行的指令。它有寄存器 保持当前的工作变量。它有一个堆栈,其中包含 执行历史,每个被调用的过程都有一帧 然而从。虽然线程必须在某个进程中执行,但是 线程和它的过程是不同的概念,是可以处理的 sepa-rately。进程用于将资源分组在一起;线程

他进一步提供了以下表格:

Per process items             | Per thread items
------------------------------|-----------------
Address space                 | Program counter
Global variables              | Registers
Open files                    | Stack
Child processes               | State
Pending alarms                |
Signals and signal handlers   |
Accounting information        |

以上就是线程工作所需要的。正如其他人指出的那样,像段这样的东西是依赖于操作系统的实现细节的。

在进程中,所有线程共享系统资源,如堆内存等,而线程有自己的堆栈

所以你的ans应该是一个进程中所有线程共享的堆内存。

除了全局内存,线程还共享许多其他属性 (也就是说,这些属性对于流程来说是全局的,而不是特定的 到一个线程)。这些属性包括:

  • 进程ID和父进程ID;
  • 进程组ID和会话ID;
  • 控制终端;
  • 进程凭据(用户和组id);
  • 打开文件描述符;
  • 使用fcntl();创建的记录锁
  • 信号处理;
  • 文件系统相关信息:umask、当前工作目录、根目录;
  • interval定时器(setitimer())和POSIX定时器(timer_create());
  • 系统V信号量撤销(semadj)值(第47.8节);
  • 资源限制;
  • CPU消耗的时间(由times()返回);
  • 所消耗的资源(由getrusage()返回);而且
  • nice值(由setpriority()nice()设置)。
对于每个线程来说,属性是不同的 : < / p >
  • 线程ID(第29.5节);
  • 信号掩码;
  • 线程特定的数据(第31.3节);
  • 交替信号堆栈(sigaltstack());
  • errno变量;
  • 浮点环境(参见fenv(3));
  • 实时调度策略和优先级(第35.2和35.3节);
  • CPU相关性(特定于linux,在第35.4节中描述);
  • 能力(linux特有的,在第39章中描述);和
  • 堆栈(局部变量和函数调用链接信息)。

节选自:《Linux编程接口:Linux和UNIX系统编程手册》,Michael Kerrisk著,第619页