线程之间共享哪些资源?
Posted
技术标签:
【中文标题】线程之间共享哪些资源?【英文标题】:What resources are shared between threads? 【发布时间】:2010-12-18 06:12:20 【问题描述】:最近,我在一次采访中被问到一个问题,进程和线程之间有什么区别。真的,我不知道答案。我想了想,给出了一个很奇怪的答案。
线程共享相同的内存,进程不共享。回答完这个问题,面试官给了我一个邪恶的笑容,向我抛出了以下问题:
问。 您知道程序被划分为哪些部分吗?
我的回答:是的(认为这很简单)堆栈、数据、代码、堆
问。 那么,告诉我:线程共享哪些片段?
我无法回答这个问题,最后把它们都说了。
请问,任何人都可以就进程和线程之间的区别给出正确且令人印象深刻的答案吗?
【问题讨论】:
线程共享相同的虚拟地址空间,进程不共享。 What is the difference between a process and a thread 的可能重复项 可能是一个迟到的答案,但它的信息非常丰富:cs.rutgers.edu/~pxk/416/notes/05-threads.html 如果是动态链接库,代码甚至可以在进程之间共享,对吗? 【参考方案1】:您说的非常正确,但线程共享所有段除了堆栈。线程具有独立的调用堆栈,但是其他线程堆栈中的内存仍然可以访问,理论上您可以在其他线程的本地堆栈帧中保存指向内存的指针(尽管您可能应该找到一个更好的地方来放置该内存!)。
【讨论】:
有趣的是,即使线程有独立的调用栈,其他栈中的内存仍然可以访问。 是的 - 我想知道在线程之间访问其他堆栈中的内存是否可以接受?只要您确定您没有尝试引用已被释放的堆栈,我不确定我是否发现它有问题? @bph:可能访问另一个线程的堆栈内存,但为了良好的软件工程实践,我不会说这是可以接受这样做。 访问,尤其是写入,其他线程的堆栈与几个垃圾收集器实现混淆。不过,这可能是 GC 实现的错误。【参考方案2】:除了全局内存,线程还共享许多其他属性 (即,这些属性对于流程来说是全局的,而不是特定的 到一个线程)。这些属性包括:
进程ID和父进程ID; 进程组 ID 和会话 ID; 控制终端; 处理凭据(用户和组 ID); 打开文件描述符; 使用fcntl();
创建的记录锁 信号配置; 文件系统相关信息:umask、当前工作目录、根目录; 间隔计时器 (setitimer()
) 和 POSIX 计时器 (timer_create()
); System V 信号量撤消 (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 章中描述);和 堆栈(局部变量和函数调用链接信息)。
摘自:The Linux Programming Interface: A Linux and UNIX System Programming Handbook , Michael Kerrisk,第 619 页
【讨论】:
【参考方案3】:在进程中所有线程共享系统资源,如堆内存等,而线程有自己的堆栈
所以你的 ans 应该是所有线程为一个进程共享的堆内存。
【讨论】:
【参考方案4】:真正需要指出的是,这个问题实际上有两个方面——理论方面和实施方面。
首先,让我们看一下理论方面。您需要从概念上了解进程是什么,以了解进程和线程之间的区别以及它们之间共享的内容。
我们从 Tanenbaum 的 Modern Operating Systems 3e 中的 2.2.2 经典线程模型部分获得以下内容:
流程模型基于两个独立的概念:资源 分组和执行。有时将它们分开是有用的; 这就是线程进来的地方......
他继续说:
查看流程的一种方式是,它是一种 将相关资源组合在一起。一个进程有一个地址空间 包含程序文本和数据,以及其他资源。这些 资源可能包括打开的文件、子进程、待处理的警报、 信号处理程序、会计信息等。通过把它们 以流程的形式组合在一起,可以更轻松地管理它们。 进程的另一个概念是执行线程,通常 缩短为线程。该线程有一个程序计数器,用于保存 跟踪接下来要执行的指令。它有寄存器,其中 保持其当前的工作变量。它有一个堆栈,其中包含 执行历史,每个过程调用一帧,但不调用 尚未归来。虽然线程必须在某个进程中执行,但 线程和它的进程是不同的概念,可以处理 分别。流程用于将资源组合在一起;线程 是计划在 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 |
以上是线程工作所需要的。正如其他人指出的那样,段之类的东西是依赖于操作系统的实现细节。
【讨论】:
这是一个很好的解释。但它可能应该以某种方式与问题联系起来,以被视为“答案” 关于表,程序计数器不是寄存器吗?以及在寄存器值中捕获的线程的“状态”?我还缺少指向它们运行的代码的指针(指向进程文本的指针)【参考方案5】:一个进程有代码、数据、堆和栈段。现在,线程或线程的指令指针 (IP) 指向进程的代码段。数据段和堆段由所有线程共享。现在堆栈区域呢?堆栈区域实际上是什么?它是由进程创建的区域,仅供其线程使用......因为堆栈可以以比堆等更快的方式使用。进程的堆栈区域在线程之间划分,即如果有 3 个线程,则进程的堆栈区被分为 3 个部分,每个部分分配给 3 个线程。换句话说,当我们说每个线程都有自己的堆栈时,该堆栈实际上是分配给每个线程的进程堆栈区域的一部分。当一个线程完成其执行时,该线程的堆栈被进程回收。事实上,不仅进程的堆栈是在线程之间划分的,而且一个线程使用的所有寄存器集合,如 SP、PC 和状态寄存器,都是进程的寄存器。 所以在共享方面,代码、数据和堆区域是共享的,而堆栈区域只是在线程之间划分。
【讨论】:
【参考方案6】:来自Wikipedia(我认为这对面试官来说是一个非常好的答案:P)
线程与传统线程不同 多任务操作系统 其中的过程:
进程通常是独立的,而线程作为一个子集存在 过程 进程携带大量状态信息,而多个线程 在进程共享状态中也是如此 作为内存和其他资源 进程有单独的地址空间,而线程共享它们的 地址空间 进程仅通过系统提供的进程间交互 沟通机制。 同一进程中线程之间的上下文切换通常更快 比之间的上下文切换 进程。
【讨论】:
关于上面第 2 点:对于线程,CPU 也维护一个上下文。【参考方案7】:一般来说,线程被称为轻量级进程。如果我们将内存分为三个部分,那么它将是:代码、数据和堆栈。 每个进程都有自己的代码、数据和堆栈部分,因此上下文切换时间有点长。为了减少上下文切换时间,人们提出了线程的概念,它与其他线程/进程共享数据和代码段,并有自己的堆栈段。
【讨论】:
你忘了堆。堆,如果我没记错的话,应该是线程间共享的【参考方案8】:在 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 中的哪一个在线程之间共享。堆管理是另一回事——它只是对操作系统的一系列调用。但是,如果您根本没有操作系统怎么办,比如在嵌入式系统中 - 您的代码中还能有 new/delete 吗?
我对年轻人的建议 - 阅读一些好的汇编编程书籍。大学课程在这方面似乎很差。
【讨论】:
【参考方案9】:告诉面试官这完全取决于操作系统的实现。
以 Windows x86 为例。只有 2 段 [1],代码和数据。它们都映射到整个 2GB(线性,用户)地址空间。基数=0,限制=2GB。他们会做一个,但 x86 不允许一个段既是读/写又是执行。所以他们做了两个,并设置 CS 指向代码描述符,其余的(DS、ES、SS 等)指向另一个 [2]。但两者都指向相同的东西!
面试你的人做了一个他/她没有陈述的隐藏假设,这是一个愚蠢的伎俩。
所以关于
问。所以告诉我哪个段线程 分享?
这些段与问题无关,至少在 Windows 上是这样。线程共享整个地址空间。只有 1 个堆栈段 SS,它指向与 DS、ES 和 CS 完全相同的东西 [2]。 IE。 整个该死的用户空间。 0-2GB。当然,这并不意味着线程只有 1 个堆栈。当然,每个都有自己的堆栈,但 x86 段不用于此目的。
也许 *nix 做了一些不同的事情。谁知道。问题所基于的前提被打破了。
-
至少对于用户空间而言。
来自
ntsd notepad
:cs=001b ss=0023 ds=0023 es=0023
【讨论】:
是的...段取决于操作系统和编译器/链接器。有时有一个独立于 DATA 段的 BSS 段。有时会出现 RODATA(像常量字符串一样的数据,可以在标记为只读的页面中)。一些系统甚至将 DATA 分解为 SMALL DATA(可从 base + 16 位偏移量访问)和(FAR)DATA(访问需要 32 位偏移量)。也可能有一个额外的 TLS DATA(线程本地存储)段,它是基于每个线程生成的 啊,不!您将段与段混淆了!部分是链接器如何将模块划分为部分(数据、rdata、文本、bss 等),如您所描述的。但我说的是 intel/amd x86 硬件中指定的段。与编译器/链接器完全无关。希望这是有道理的。 但是,Adisak 对 Thread Local 商店的看法是正确的。它是线程私有的,不共享。我知道 Windows 操作系统,但不确定其他操作系统。【参考方案10】:线程共享一切 [1]。整个过程只有一个地址空间。
每个线程都有自己的堆栈和寄存器,但所有线程的堆栈在共享地址空间中都是可见的。
如果一个线程在其堆栈上分配了某个对象,并将地址发送给另一个线程,则它们都将拥有对该对象的同等访问权限。
实际上,我刚刚注意到一个更广泛的问题:我认为您混淆了 segment 这个词的两种用法。
可执行文件(例如,ELF)的文件格式中有不同的部分,可以称为段,包含编译代码(文本)、初始化数据、链接器符号、调试信息等。没有堆或堆栈段,因为它们是仅运行时的构造。
这些二进制文件段可以单独映射到进程地址空间,具有不同的权限(例如,代码/文本的只读可执行文件和初始化数据的写时复制不可执行)。
按照惯例(由您的语言运行时库强制执行),此地址空间的区域用于不同目的,例如堆分配和线程堆栈。但这只是内存,除非您在虚拟 8086 模式下运行,否则可能不会分段。每个线程的堆栈都是在线程创建时分配的一块内存,当前堆栈顶部地址存储在堆栈指针寄存器中,每个线程都将自己的堆栈指针与其他寄存器一起保存。
[1] 好的,我知道:信号掩码、TSS/TSD 等。地址空间,包括所有映射的程序段,仍然是共享的。
【讨论】:
【参考方案11】:线程共享数据和代码,而进程不共享。堆栈不为两者共享。
进程也可以共享内存,更准确地说是代码,例如在Fork()
之后,但这是一个实现细节和(操作系统)优化。多个进程共享的代码将(希望)在第一次写入代码时被复制——这被称为copy-on-write。我不确定线程代码的确切语义,但我假设是共享代码。
1 代码逻辑上是私有的,但出于性能原因可能会被共享。 2 我不是 100% 确定。
【讨论】:
我会说代码段(文本段)与数据不同,在大多数架构上几乎总是只读的。【参考方案12】:线程共享堆(有关于线程特定堆的研究)但当前实现共享堆。 (当然还有代码)
【讨论】:
【参考方案13】:线程共享代码和数据段以及堆,但它们不共享堆栈。
【讨论】:
“能够访问堆栈中的数据”和共享堆栈是有区别的。这些线程有自己的堆栈,在调用方法时会被推送和弹出。 它们都是同样有效的视图。是的,每个线程都有自己的堆栈,因为线程和堆栈之间存在一对一的对应关系,并且每个线程都有一个空间用于自己的正常堆栈使用。但它们也是完全共享的进程资源,如果需要,任何线程都可以像访问自己的一样轻松访问任何其他线程的堆栈。 @DavidSchwartz,我可以将您的观点总结如下:每个线程都有自己的堆栈,堆栈由两部分组成 - 在进程多线程之前线程之间共享的第一部分,以及在拥有线程运行时填充的第二部分。同意吗? @nextTide 没有两部分。堆栈是共享的,期间。每个线程都有自己的堆栈,但它们也是共享的。也许一个很好的类比是,如果您和您的妻子各有一辆汽车,但您可以随时使用彼此的汽车。以上是关于线程之间共享哪些资源?的主要内容,如果未能解决你的问题,请参考以下文章