关于多线程:线程之间共享哪些资源?

What resources are shared between threads?

最近,我在面试中被问到一个问题,即流程和线程之间的区别。 真的,我不知道答案。 我想了一会儿,给了一个非常奇怪的答案。

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

问:你知道程序划分的部分吗?

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

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

我无法回答这个问题并最终说出了所有这些。

请问,任何人都可以为流程和线程之间的差异提供正确和令人印象深刻的答案吗?


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


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

Threads differ from traditional
multitasking operating system
processes in that:

  • processes are typically independent, while threads exist as subsets of a
    process
  • processes carry considerable state information, whereas multiple threads
    within a process share state as well
    as memory and other resources
  • processes have separate address spaces, whereas threads share their
    address space
  • processes interact only through system-provided inter-process
    communication mechanisms.
  • Context switching between threads in the same process is typically faster
    than context switching between
    processes.


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

首先,让我们看一下理论方面。您需要了解流程在概念上是什么来理解流程和线程之间的区别以及它们之间共享的内容。

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

The process model is based on two independent concepts: resource
grouping and execution. Sometimes it is use-ful to separate them;
this is where threads come in....

他继续:

One way of looking at a process is that it is a way to
group related resources together. A process has an address space
containing program text and data, as well as other resources. These
resource may include open files, child processes, pending alarms,
signal handlers, accounting information, and more. By putting them
together in the form of a process, they can be managed more easily.
The other concept a process has is a thread of execution, usually
shortened to just thread. The thread has a program counter that keeps
track of which instruc-tion to execute next. It has registers, which
hold its current working variables. It has a stack, which contains the
execution history, with one frame for each proce-dure called but not
yet returned from. Although a thread must execute in some process, the
thread and its process are different concepts and can be treated
sepa-rately. Processes are used to group resources together; threads
are the entities scheduled for execution on the CPU.

再向下,他提供了下表:

1
2
3
4
5
6
7
8
9
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        |

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


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

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

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

所以关于

Q. So tell me which segment thread
share?

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

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

  • 至少对于用户空间。
  • ntsd notepadcs=001b ss=0023 ds=0023 es=0023

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


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


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


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

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

    1
    2
    3
    4
    5
               Process   Thread

       Stack   private   private
       Data    private   shared
       Code    private1  shared2

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


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

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

    如果一个线程在其堆栈上分配了一些对象,并将该地址发送到另一个线程,那么它们都将具有对该对象的相同访问权限。

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

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

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

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

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


    在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中的哪一个。堆管理是另一回事 - 它只是对操作系统的一系列调用。但是,如果您根本没有操作系统,例如在嵌入式系统中,您还可以在代码中使用新的/删除操作吗?

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


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


    Besides global memory, threads also share a number of other attributes
    (i.e., these attributes are global to a process, rather than specific
    to a thread). These attributes include the following:

    • process ID and parent process ID;
    • process group ID and session ID;
    • controlling terminal;
    • process credentials (user and group IDs);
    • open file descriptors;
    • record locks created using fcntl();
    • signal dispositions;
    • file system–related information: umask, current working directory, and root directory;
    • interval timers (setitimer()) and POSIX timers (timer_create());
    • System V semaphore undo (semadj) values (Section 47.8);
    • resource limits;
    • CPU time consumed (as returned by times());
    • resources consumed (as returned by getrusage()); and
    • nice value (set by setpriority() and nice()).

    Among the attributes that are distinct for each thread are the
    following:

    • thread ID (Section 29.5);
    • signal mask;
    • thread-specific data (Section 31.3);
    • alternate signal stack (sigaltstack());
    • the errno variable;
    • floating-point environment (see fenv(3));
    • realtime scheduling policy and priority (Sections 35.2 and 35.3);
    • CPU affinity (Linux-specific, described in Section 35.4);
    • capabilities (Linux-specific, described in Chapter 39); and
    • stack (local variables and function call linkage information).

    摘录自:Linux编程接口:Linux和UNIX系统编程手册,Michael Kerrisk,第619页


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

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