Quelles ressources sont partagées entre les threads?

264

Récemment, on m'a posé une question dans une interview quelle est la différence entre un processus et un fil. Vraiment, je ne connaissais pas la réponse. J'ai réfléchi une minute et j'ai donné une réponse très bizarre.

Les threads partagent la même mémoire, pas les processus. Après avoir répondu à cela, l'intervieweur m'a fait un sourire diabolique et m'a tiré les questions suivantes:

Q. Connaissez-vous les segments dans lesquels un programme est divisé?

Ma réponse: oui (je pensais que c'était facile) Stack, Data, Code, Heap

Q. Alors, dites-moi: quels segments les threads partagent-ils?

Je n'ai pas pu répondre à cette question et j'ai fini par les dire tous.

S'il vous plaît, quelqu'un peut-il présenter les réponses correctes et impressionnantes pour la différence entre un processus et un fil?

Xinus
la source
10
Les threads partagent le même espace d'adressage virtuel , pas le processus.
Benoit

Réponses:

177

Vous avez à peu près raison, mais les threads partagent tous les segments sauf la pile. Les threads ont des piles d'appels indépendantes, mais la mémoire des autres piles de threads est toujours accessible et en théorie, vous pouvez conserver un pointeur sur la mémoire dans le cadre de pile local d'un autre thread (bien que vous devriez probablement trouver un meilleur endroit pour mettre cette mémoire!).

Greg Hewgill
la source
28
La partie intéressante est que même si les threads ont des piles d'appels indépendantes, la mémoire des autres piles est toujours accessible.
Karthik Balaguru
1
oui - je me demande s'il est acceptable d'accéder à la mémoire dans d'autres piles entre les threads? Tant que vous êtes sûr que vous n'essayez pas de référencer une pile qui a été désallouée, je ne suis pas sûr de voir un problème avec elle?
bph
2
@bph: Il est possible d'accéder à la mémoire de pile d'un autre thread, mais dans l'intérêt de bonnes pratiques d'ingénierie logicielle, je ne dirais pas qu'il est acceptable de le faire.
Greg Hewgill
1
L'accès, en particulier l'écriture à, les piles d'autres threads mess avec plusieurs implémentations de garbage collector. Cependant, cela pourrait être justifié comme une faute de la mise en œuvre du GC.
yyny
56

De Wikipedia (je pense que cela ferait une très bonne réponse pour l'intervieweur: P)

Les threads diffèrent des processus traditionnels du système d'exploitation multitâche en ce que:

  • les processus sont généralement indépendants, tandis que les threads existent en tant que sous-ensembles d'un processus
  • les processus contiennent des informations d'état considérables, tandis que plusieurs threads dans un état de partage de processus ainsi que la mémoire et d'autres ressources
  • les processus ont des espaces d'adressage séparés, tandis que les threads partagent leur espace d'adressage
  • les processus n'interagissent que par le biais de mécanismes de communication interprocessus fournis par le système.
  • La commutation de contexte entre les threads dans le même processus est généralement plus rapide que la commutation de contexte entre les processus.
Jorge Córdoba
la source
2
à propos du point n ° 2 ci-dessus: pour les threads, le CPU conserve également un contexte.
Jack
49

Ce qu'il faut vraiment souligner, c'est qu'il y a vraiment deux aspects à cette question - l'aspect théorique et l'aspect des implémentations.

Voyons d'abord l'aspect théorique. Vous devez comprendre ce qu'est un processus sur le plan conceptuel pour comprendre la différence entre un processus et un thread et ce qui est partagé entre eux.

Nous avons ce qui suit de la section 2.2.2 Le modèle de fil classique dans les systèmes d'exploitation modernes 3e par Tanenbaum:

Le modèle de processus est basé sur deux concepts indépendants: le regroupement et l'exécution des ressources. Parfois, il est utile de les séparer; c'est là que les fils entrent en jeu ....

Il continue:

Une façon d'envisager un processus est qu'il s'agit d'un moyen de regrouper des ressources connexes. Un processus a un espace d'adressage contenant le texte et les données du programme, ainsi que d'autres ressources. Ces ressources peuvent inclure des fichiers ouverts, des processus enfants, des alarmes en attente, des gestionnaires de signaux, des informations comptables, etc. En les regroupant sous forme de processus, ils peuvent être gérés plus facilement. L'autre concept d'un processus est un thread d'exécution, généralement abrégé en juste thread. Le thread a un compteur de programme qui garde la trace de l'instruction à exécuter ensuite. Il a des registres, qui contiennent ses variables de travail actuelles. Il a une pile, qui contient l'historique d'exécution, avec une trame pour chaque procédure appelée mais pas encore retournée. Bien qu'un thread doive s'exécuter dans un processus, le fil et son processus sont des concepts différents et peuvent être traités séparément. Les processus sont utilisés pour regrouper les ressources; les threads sont les entités dont l'exécution est planifiée sur le processeur.

Plus loin, il fournit le tableau suivant:

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        |

Ce qui précède est ce dont vous avez besoin pour que les threads fonctionnent. Comme d'autres l'ont souligné, des éléments comme les segments sont des détails d'implémentation dépendants du système d'exploitation.

Robert S. Barnes
la source
2
Ceci est une excellente explication. Mais cela devrait probablement être lié à la question d'une manière ou d'une autre pour être considéré comme une "réponse"
catalyseur294
Concernant le tableau, le programme n'est-il pas un registre? et "l'état" d'un thread, capturé dans la valeur des registres? Il me manque également le pointeur sur le code qu'ils exécutent (pointeur sur le texte du processus)
onlycparra
29

Dites à l'intervieweur que cela dépend entièrement de la mise en œuvre du système d'exploitation.

Prenez Windows x86 par exemple. Il n'y a que 2 segments [1], Code et Data. Et ils sont tous deux mappés à tout l'espace d'adressage de 2 Go (linéaire, utilisateur). Base = 0, limite = 2 Go. Ils en auraient fait un mais x86 ne permet pas qu'un segment soit à la fois en lecture / écriture et en exécution. Ils en ont donc créé deux et ont défini CS pour pointer vers le descripteur de code, et le reste (DS, ES, SS, etc.) pour pointer vers l'autre [2]. Mais les deux pointent vers les mêmes choses!

La personne qui vous a interviewé a fait une supposition cachée qu'elle n'a pas déclaré, et c'est une astuce stupide à tirer.

Donc en ce qui concerne

Q. Alors, dites-moi quel segment de thread partager?

Les segments ne sont pas pertinents pour la question, au moins sous Windows. Les threads partagent tout l'espace d'adressage. Il n'y a qu'un seul segment de pile, SS, et il pointe exactement vers les mêmes choses que DS, ES et CS [2]. C'est-à-dire tout l'espace utilisateur sanglant . 0-2 Go. Bien sûr, cela ne signifie pas que les threads n'ont qu'une seule pile. Naturellement, chacun a sa propre pile, mais les segments x86 ne sont pas utilisés à cette fin.

Peut-être que * nix fait quelque chose de différent. Qui sait. La prémisse sur laquelle la question était fondée était brisée.


  1. Au moins pour l'espace utilisateur.
  2. De ntsd notepad:cs=001b ss=0023 ds=0023 es=0023
Alex Budovski
la source
1
Oui ... Les segments dépendent du système d'exploitation et du compilateur / éditeur de liens. Parfois, il existe un segment BSS distinct du segment DATA. Parfois, il y a RODATA (des données comme des chaînes constantes qui peuvent être dans des pages marquées en lecture seule). Certains systèmes divisent même les DONNÉES en PETITES DONNÉES (accessibles à partir d'une base + décalage 16 bits) et (FAR) DONNÉES (décalage 32 bits requis pour accéder). Il est également possible qu'il existe un segment TLS DATA (magasin local de threads) supplémentaire qui est généré sur une base par thread
Adisak
5
Ah non! Vous confondez segments et sections! Les sections sont la façon dont l'éditeur de liens divise le module en parties (données, données rdata, texte, bss, etc.) comme vous l'avez décrit. Mais je parle de segments, comme spécifié dans le matériel intel / amd x86. Pas du tout lié aux compilateurs / linkers. J'espère que cela a du sens.
Alex Budovski
Cependant, Adisak a raison sur le magasin Thread Local. Il est privé du thread et n'est pas partagé. Je connais le système d'exploitation Windows et je ne suis pas sûr des autres systèmes d'exploitation.
Jack
20

Généralement, les threads sont appelés processus légers. Si nous divisons la mémoire en trois sections, ce sera: Code, données et pile. Chaque processus a ses propres sections de code, de données et de pile et, en raison de ce contexte, le temps de changement de contexte est un peu élevé. Pour réduire le temps de changement de contexte, les gens ont trouvé un concept de thread, qui partage le segment de données et de code avec d'autres threads / processus et il a son propre segment STACK.

Nimish Thakkar
la source
Vous avez oublié le tas. Le tas, si je ne me trompe pas, devrait être partagé entre les threads
Phate
20

Un processus a des segments de code, de données, de tas et de pile. Maintenant, le pointeur d'instruction (IP) d'un thread OU threads pointe vers le segment de code du processus. Les segments de données et de segment de mémoire sont partagés par tous les threads. Qu'en est-il maintenant de la zone de pile? Quelle est en réalité la zone de pile? C'est une zone créée par le processus uniquement pour son thread à utiliser ... car les piles peuvent être utilisées de manière beaucoup plus rapide que les tas, etc. La zone de pile du processus est divisée en threads, c'est-à-dire s'il y a 3 threads, alors le la zone de pile du processus est divisée en 3 parties et chacune est donnée aux 3 fils. En d'autres termes, lorsque nous disons que chaque thread a sa propre pile, cette pile fait en fait partie de la zone de pile de processus allouée à chaque thread. Lorsqu'un thread termine son exécution, la pile du thread est récupérée par le processus. En réalité, non seulement la pile d'un processus est divisée entre les threads, mais l'ensemble des registres qu'un thread utilise comme SP, PC et les registres d'état sont les registres du processus. Ainsi, en matière de partage, le code, les données et les zones de tas sont partagés, tandis que la zone de pile est simplement divisée entre les threads.

Dhirendra Vikash Sharma
la source
13

Les threads partagent le code et les segments de données et le tas, mais ils ne partagent pas la pile.

Kevin Peterson
la source
11
Il y a une différence entre "pouvoir accéder aux données de la pile" et partager la pile. Ces threads ont leurs propres piles qui sont poussées et sautées lors de l'appel de méthodes.
Kevin Peterson
2
Ce sont deux vues tout aussi valables. Oui, chaque thread a sa propre pile dans le sens où il existe une correspondance biunivoque entre les threads et les piles et chaque thread a un espace qu'il utilise pour sa propre utilisation normale de la pile. Mais ce sont également des ressources de processus entièrement partagées et, si vous le souhaitez, n'importe quel thread peut accéder à la pile de n'importe quel autre thread aussi facilement que la sienne.
David Schwartz
@DavidSchwartz, puis-je résumer votre point comme ci-dessous: Chaque thread a sa propre pile, et la pile se compose de 2 parties - la première partie qui est partagée entre les threads avant que le processus ne soit multithread, et la deuxième partie qui est remplie lorsque le thread propriétaire est en cours d'exécution. D'accord?
FaceBro
2
@nextTide Il n'y a pas deux parties. Les piles sont partagées, point final. Chaque thread a sa propre pile, mais ils sont également partagés. Une bonne analogie est peut-être si vous êtes et que votre femme a chacune une voiture, mais vous pouvez utiliser les voitures de chacun à tout moment.
David Schwartz
5

Les threads partagent des données et du code, contrairement aux processus. La pile n'est pas partagée pour les deux.

Les processus peuvent également partager de la mémoire, plus précisément du code, par exemple après un Fork(), mais il s'agit d'un détail d'implémentation et d'une optimisation (du système d'exploitation). Le code partagé par plusieurs processus sera (espérons-le) dupliqué lors de la première écriture dans le code - c'est ce qu'on appelle la copie sur écriture . Je ne suis pas sûr de la sémantique exacte du code des threads, mais je suppose que le code est partagé.

           Fil de processus

   Empiler privé privé
   Données privées partagées
   Code privé 1   partagé 2

1 Le code est logiquement privé mais peut être partagé pour des raisons de performances. 2 Je ne suis pas sûr à 100%.

Daniel Brückner
la source
Je dirais que le segment de code (segment de texte), contrairement aux données, est presque toujours en lecture seule sur la plupart des architectures.
Jorge Córdoba
4

Les discussions partagent tout [1]. Il existe un espace d'adressage pour l'ensemble du processus.

Chaque thread a sa propre pile et ses propres registres, mais les piles de tous les threads sont visibles dans l'espace d'adressage partagé.

Si un thread alloue un objet sur sa pile et envoie l'adresse à un autre thread, ils auront tous deux un accès égal à cet objet.


En fait, je viens de remarquer un problème plus large: je pense que vous confondez deux utilisations du segment de mot .

Le format de fichier d'un exécutable (par exemple, ELF) comporte des sections distinctes, qui peuvent être appelées segments, contenant du code compilé (texte), des données initialisées, des symboles de l'éditeur de liens, des informations de débogage, etc. Il n'y a pas de segments de tas ou de pile. ici, car ce sont des constructions uniquement à l'exécution.

Ces segments de fichiers binaires peuvent être mappés séparément dans l'espace d'adressage du processus, avec des autorisations différentes (par exemple, un exécutable en lecture seule pour le code / texte et un non exécutable en copie sur écriture pour les données initialisées).

Les zones de cet espace d'adressage sont utilisées à des fins différentes, comme l'allocation de segments de mémoire et les piles de threads, par convention (appliquées par vos bibliothèques d'exécution de langage). Cependant, il ne s'agit que de mémoire et probablement pas segmenté, sauf si vous exécutez en mode 8086 virtuel. La pile de chaque thread est un bloc de mémoire alloué au moment de la création du thread, l'adresse de la pile actuelle étant stockée dans un registre de pointeur de pile, et chaque thread conserve son propre pointeur de pile avec ses autres registres.


[1] D'accord, je sais: masques de signaux, TSS / TSD etc. L'espace d'adressage, y compris tous ses segments de programme mappés, est cependant toujours partagé.

Inutile
la source
3

Dans un framework x86, on peut diviser autant de segments (jusqu'à 2 ^ 16-1). Les directives ASM SEGMENT / ENDS le permettent, et les opérateurs SEG et OFFSET permettent l'initialisation des registres de segments. CS: IP sont généralement initialisés par le chargeur, mais pour DS, ES, SS, l'application est responsable de l'initialisation. De nombreux environnements permettent les soi-disant "définitions de segment simplifiées" comme .code, .data, .bss, .stack etc. et, en fonction également du "modèle de mémoire" (petit, grand, compact, etc.) le chargeur initialise les registres de segment en conséquence. Habituellement .data, .bss, .stack et autres segments habituels (je ne l'ai pas fait depuis 20 ans donc je ne me souviens pas de tout) sont regroupés en un seul groupe - c'est pourquoi généralement DS, ES et SS pointent vers le même domaine, mais ce n'est que pour simplifier les choses.

En général, tous les registres de segments peuvent avoir des valeurs différentes lors de l'exécution. Donc, la question de l'entretien était juste: lequel des CODE, DATA et STACK est partagé entre les threads. La gestion de tas est autre chose - c'est simplement une séquence d'appels au système d'exploitation. Mais que se passe-t-il si vous n'avez pas du tout d'OS, comme dans un système embarqué - pouvez-vous toujours avoir new / delete dans votre code?

Mon conseil aux jeunes - lisez un bon livre de programmation d'assemblage. Il semble que les programmes universitaires soient assez pauvres à cet égard.

George
la source
2

Outre la mémoire globale, les threads partagent également un certain nombre d'autres attributs (c'est-à-dire que ces attributs sont globaux pour un processus, plutôt que spécifiques à un thread). Ces attributs sont les suivants:

  • ID de processus et ID de processus parent;
  • ID de groupe de processus et ID de session;
  • terminal de contrôle;
  • informations d'identification de processus (identifiants d'utilisateur et de groupe);
  • descripteurs de fichiers ouverts;
  • verrous d'enregistrement créés à l'aide fcntl();
  • dispositions des signaux;
  • informations relatives au système de fichiers: umask, répertoire de travail actuel et répertoire racine;
  • temporisateurs d'intervalle ( setitimer()) et temporisateurs POSIX ( timer_create());
  • semadjValeurs undo ( ) du sémaphore du système V (section 47.8);
  • limites des ressources;
  • Temps CPU consommé (tel que renvoyé par times());
  • les ressources consommées (telles que rendues par getrusage()); et
  • belle valeur (définie par setpriority()et nice()).

Les attributs qui sont distincts pour chaque thread sont les suivants:

  • ID de thread (section 29.5);
  • masque de signalisation;
  • données spécifiques au thread (section 31.3);
  • pile de signaux alternative ( sigaltstack());
  • la variable errno;
  • environnement à virgule flottante (voir fenv(3));
  • politique et priorité d'ordonnancement en temps réel (articles 35.2 et 35.3);
  • Affinité CPU (spécifique à Linux, décrit à la section 35.4);
  • capacités (spécifiques à Linux, décrites au chapitre 39); et
  • pile (variables locales et informations de liaison d'appel de fonction).

Extrait de: The Linux Programming Interface: A Linux and UNIX System Programming Handbook, Michael Kerrisk , page 619

snr
la source
0

Le thread partage le tas (il existe des recherches sur le tas spécifique au thread) mais l'implémentation actuelle partage le tas. (et bien sûr le code)

Dani
la source
0

En cours, tous les threads partagent des ressources système comme la mémoire de tas, etc. tandis que le thread a sa propre pile

Ainsi, votre ans doit être une mémoire de tas que tous les threads partagent pour un processus.

roshni
la source