线程之间共享哪些资源?


264

最近,在一次采访中有人问我一个问题,流程和线程之间有什么区别。真的,我不知道答案。我想了一会儿,给出了一个很奇怪的答案。

线程共享相同的内存,进程不共享。在回答了这个问题之后,面试官给了我一个邪恶的微笑,并向我提出了以下问题:

:您知道程序被划分为哪些部分吗?

我的回答:是的(这很简单)堆栈,数据,代码,堆

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

我无法回答这个问题,最后全部说了出来。

请问,有人可以针对进程和线程之间的差异给出正确而令人印象深刻的答案吗?


9
线程共享相同的虚拟地址空间,进程不共享。
Benoit

Answers:


177

您非常正确,但是线程共享堆栈之外的所有段。线程具有独立的调用堆栈,但是其他线程堆栈中的内存仍然可以访问,并且理论上您可以在其他线程的本地堆栈帧中持有指向内存的指针(尽管您可能应该找到放置该内存的更好位置!)。


27
有趣的是,即使线程具有独立的调用堆栈,其他堆栈中的内存仍可访问。
Karthik Balaguru 2014年

1
是的-我想知道在线程之间访问其他堆栈中的内存是否可以接受?只要您确定不尝试引用已被释放的堆栈,就可以确定它是否存在问题?
bph

2
@bph:可以访问另一个线程的堆栈内存,但是出于良好的软件工程实践的利益,我不认为这样做是可以接受的
Greg Hewgill '18

1
访问(尤其是写入)其他线程的堆栈会导致一些垃圾回收器实现混乱。不过,这可以说是GC实施的错误。
yyny19年

56

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

线程与传统的多任务操作系统过程的不同之处在于:

  • 进程通常是独立的,而线程作为进程的子集存在
  • 进程携带大量状态信息,而一个进程中的多个线程共享状态以及内存和其他资源
  • 进程具有单独的地址空间,而线程共享其地址空间
  • 进程仅通过系统提供的进程间通信机制进行交互。
  • 同一进程中线程之间的上下文切换通常比进程之间的上下文切换快。

2
关于上面第2点:对于线程,CPU也会维护一个上下文。
杰克

49

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

首先,让我们看一下理论方面。您需要了解流程在概念上的含义,以了解流程与线程之间的区别以及它们之间的共享。

我们从部分以下2.2.2古典线程模型现代操作系统3E通过的Tanenbaum:

流程模型基于两个独立的概念:资源分组和执行。有时将它们分开很有用;这是线程进入的地方。

他继续:

查看流程的一种方式是将相关资源组合在一起。进程的地址空间包含程序文本和数据以及其他资源。这些资源可能包括打开的文件,子进程,挂起的警报,信号处理程序,记帐信息等。通过以流程的形式将它们组合在一起,可以更轻松地对其进行管理。进程具有的另一个概念是执行线程,通常简称为线程。该线程具有程序计数器,该计数器跟踪接下来要执行的指令。它具有寄存器,用于保存其当前的工作变量。它有一个堆栈,其中包含执行历史记录,每个调用但尚未返回的过程都有一帧。尽管线程必须在某些进程中执行,线程及其过程是不同的概念,可以分别处理。流程用于将资源分组在一起;线程是计划在CPU上执行的实体。

在下方,他提供了下表:

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        |

以上是线程工作所需的条件。正如其他人指出的那样,诸如段之类的东西是依赖于操作系统的实现细节。


2
这是一个很好的解释。但是它应该以某种方式与被认为是“答案”的问题相关联
催化剂

关于表格,程序不是计数器吗?以及线程的“状态”,是否在寄存器的值中捕获?我也缺少指向它们运行的​​代码的指针(指向过程文本的指针)
Onlycparra

29

告诉采访者,这完全取决于操作系统的实现。

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

采访您的人隐瞒了一个假设,即他/她没有陈述,这是一个愚蠢的trick俩。

所以关于

问:那么告诉我哪个段线程共享?

这些段与问题无关,至少在Windows上是这样。线程共享整个地址空间。只有1个堆栈段SS,它指向与DS,ES和CS完全相同的内容[2]。即整个流血的用户空间。0-2GB。当然,这并不意味着线程只有1个堆栈。当然每个都有自己的堆栈,但是x86段并不用于此目的。

也许* nix做一些不同的事情。谁知道。问题所基于的前提已被打破。


  1. 至少对于用户空间。
  2. 来自ntsd notepadcs=001b ss=0023 ds=0023 es=0023

1
是的...段取决于操作系统和编译器/链接器。有时,有一个与数据段分开的BSS段。有时会有RODATA(常量字符串之类的数据可以位于标记为只读的页面中)。某些系统甚至将DATA分为小数据(可从基数+ 16位偏移量访问)和(FAR)数据(访问所需的32位偏移量)。也有可能在每个线程的基础上生成额外的TLS DATA(线程本地存储)段
Adisak 2009年

5
啊,不!您正在将细分与版块混淆!章节介绍了链接器如何将模块划分为各个部分(数据,rdata,文本,bss等)。但是我在说的是intel / amd x86硬件中指定的段。与编译器/链接器完全无关。希望有道理。
2009年

但是,Adisak关于Thread Local存储是正确的。它是线程专用的,不共享。我知道Windows操作系统,但不确定其他操作系统。
2013年

20

通常,线程称为轻量进程。如果我们将内存分为三部分,那么它将是:代码,数据和堆栈。每个进程都有自己的代码,数据和堆栈部分,由于上下文的关系,切换时间有点长。为了减少上下文切换时间,人们想到了线程的概念,该线程与其他线程/进程共享数据和代码段,并且具有自己的STACK段。


你忘了堆。如果我没记错的话,堆应该在线程之间共享
Phate

20

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


13

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


11
在“能够访问堆栈中的数据”与共享堆栈之间存在区别。这些线程具有自己的堆栈,在调用方法时会被压入并弹出。
凯文·彼得森

2
它们都是同等有效的视图。是的,每个线程都有自己的堆栈,这意味着线程和堆栈之间存在一对一的对应关系,并且每个线程都有一个空间供其自己的正常堆栈使用。但是它们也是完全共享的进程资源,如果需要,任何线程都可以像访问自己的线程一样轻松地访问任何其他线程的堆栈。
David Schwartz

@DavidSchwartz,我可以总结一下以下几点:每个线程都有自己的堆栈,该堆栈由2部分组成-第一部分在多线程处理之前在线程之间共享,第二部分在执行多线程操作时填充拥有的线程正在运行..同意吗?
FaceBro 2015年

2
@nextTide没有两部分。堆栈是共享的,期限。每个线程都有自己的堆栈,但也可以共享。也许是一个很好的类比,如果您和您的妻子各自都有汽车,但是您可以随时使用彼此的汽车。
David Schwartz 2015年

5

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

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

           进程线程

   堆叠私人私人
   私人数据共享
   私密代码1   共享2

1该代码在逻辑上是私有的,但出于性能原因可以共享。 2我不确定100%。


我想说代码段(文本段)与数据不同,在大多数体系结构上几乎总是只读的。
豪尔赫·科尔多瓦

4

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

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

如果一个线程在其堆栈上分配某个对象,然后将地址发送给另一线程,则它们都将对该对象具有相同的访问权限。


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

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

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

按照惯例(由您的语言运行时库强制执行),该地址空间的区域用于不同目的,例如堆分配和线程堆栈。但是,它们全都只是内存,除非您在虚拟8086模式下运行,否则可能不会分段。每个线程的堆栈都是在线程创建时分配的内存块,当前堆栈的最高地址存储在堆栈指针寄存器中,并且每个线程与其他寄存器一起保留自己的堆栈指针。


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


3

在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通常指向teh相同的区域,但这只是为了简化事情。

通常,所有段寄存器在运行时可以具有不同的值。因此,采访问题是正确的:线程之间共享CODE,DATA和STACK中的哪一个。堆管理是另一回事-它只是对操作系统的一系列调用。但是,如果根本没有像嵌入式系统那样的OS,该怎么办?您的代码中还能包含new / delete吗?

我对年轻人的建议-阅读一些好的汇编程序设计书。在这方面,大学课程似乎很差。


2

除了全局内存,线程还共享许多其他属性(即,这些属性对于进程是全局的,而不是特定于线程的)。这些属性包括:

  • 进程ID和父进程ID;
  • 进程组ID和会话ID;
  • 控制终端;
  • 处理凭据(用户和组ID);
  • 打开文件描述符;
  • 记录使用创建的锁 fcntl();
  • 信号处置;
  • 文件系统相关信息:umask,当前工作目录和根目录;
  • 间隔计时器(setitimer())和POSIX计时器(timer_create());
  • 系统V信号量undo(semadj)值(第47.8节);
  • 资源限制;
  • 消耗的CPU时间(由返回times());
  • 消耗的资源(由返回getrusage());和
  • 不错的值(由setpriority()和设置nice())。

对于每个线程,不同的属性如下:

  • 线程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页


0

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


0

在处理过程中,所有线程共享系统资源(例如堆内存等),而线程拥有自己的堆栈

因此,ans应该是所有线程共享一个进程的堆内存。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.