Les threads ont-ils un tas distinct?

114

Pour autant que je sache, chaque thread obtient une pile distincte lorsque le thread est créé par le système d'exploitation. Je me demande si chaque thread a un tas distinct de lui-même également?

Martin Thoma
la source
oui, windows et linux, bibliothèque c
3
Agréable. +1 garder ces questions fondamentales à venir.

Réponses:

128

Non. Tous les threads partagent un tas commun.

Chaque thread a une pile privée , dont il peut rapidement ajouter et supprimer des éléments. Cela rend la mémoire basée sur la pile rapide, mais si vous utilisez trop de mémoire de pile, comme cela se produit dans la récursivité infinie, vous obtiendrez un débordement de pile.

Comme tous les threads partagent le même tas, l'accès à l'allocateur / désallocateur doit être synchronisé. Il existe différentes méthodes et bibliothèques pour éviter les conflits d' allocateurs .

Certains langages vous permettent de créer des pools de mémoire privés, ou des tas individuels, que vous pouvez attribuer à un seul thread.

Brianegge
la source
5
En règle générale, les threads partagent des ressources, telles que la mémoire, de sorte que toute implémentation de thread non-braindead partagerait le tas.
R. Martinho Fernandes
10
La principale raison pour laquelle chaque thread a sa propre pile est que le thread puisse réellement faire quelque chose (comme appeler une fonction) ...
Edmund
3
Chaque thread a une pile distincte, mais elle n'est pas nécessairement «privée». Les autres threads sont généralement autorisés à y accéder.
zch
you will get a stack overflow.Un débordement de pile sur Stack Overflow!
John Strood
2
@crisron Il est possible de configurer un tas séparé pour chaque thread, mais si vous faites cela plutôt que d'utiliser le tas partagé par défaut, il devient alors difficile pour le thread A, par exemple, d'allouer un tampon, de le remplir de données, de le transmettre au thread B et que le thread B utilise les données, puis libère le tampon (parce que le thread B n'a pas accès au tas du thread A, le thread B ne peut pas libérer le tampon; le meilleur thread B pourrait faire est de renvoyer le tampon au thread A à nouveau et laissez le fil A le libérer).
Jeremy Friesner
9

Par défaut, C n'a qu'un seul tas.

Cela dit, certains allocateurs qui sont conscients des threads partitionneront le tas de sorte que chaque thread ait sa propre zone d'allocation. L'idée est que cela devrait améliorer l'échelle du tas.

Un exemple d'un tel tas est Hoard .

R Samuel Klatchko
la source
Par défaut, C et C ++ n'ont pas plusieurs threads. La spécification c ++ de 2003 au moins ne permet pas les threads dans sa conception de machine virtuelle, donc les threads, en C ++, sont définis par l'implémentation.
Chris Becke
Même si différents threads ont des zones différentes à allouer sur le tas, ils peuvent toujours voir les données allouées par un autre thread, de sorte que les threads partagent toujours le même tas.
Ken Bloom
1
Mise à jour: à partir de C ++ 11, les threads ne sont plus définis par l'implémentation.
Michael Dorst
5

Dépend du système d'exploitation. Le runtime c standard sur Windows et Unices utilise un tas partagé entre les threads. Cela signifie verrouiller chaque malloc / free.

Sur Symbian, par exemple, chaque thread est livré avec son propre tas, bien que les threads puissent partager des pointeurs vers des données allouées dans n'importe quel tas. La conception de Symbian est meilleure à mon avis car elle élimine non seulement le besoin de verrouillage pendant alloc / free, mais encourage également une spécification claire de la propriété des données entre les threads. Dans ce cas également, lorsqu'un thread meurt, il emporte avec lui tous les objets qu'il a alloués - c'est-à-dire qu'il ne peut pas divulguer les objets qu'il a alloués, ce qui est une propriété importante à avoir dans les appareils mobiles avec une mémoire contrainte.

Erlang suit également une conception similaire où un «processus» agit comme une unité de ramassage des ordures. Toutes les données sont communiquées entre les processus par copie, à l'exception des blobs binaires qui sont comptés par référence (je pense).

Srikumar
la source
3

Chaque thread a sa propre pile et sa propre pile d'appels.

Chaque thread partage le même tas.

tsalter
la source
3

Cela dépend de ce que vous voulez dire exactement lorsque vous dites "tas".

Tous les threads partagent l'espace d'adressage, de sorte que les objets alloués au tas sont accessibles à partir de tous les threads. Techniquement, les piles sont également partagées dans ce sens, c'est-à-dire que rien ne vous empêche d'accéder à la pile d'autres threads (même si cela n'aurait presque jamais de sens de le faire).

D'autre part, il existe des structures de tas utilisées pour allouer de la mémoire. C'est là que toute la comptabilité pour l'allocation de mémoire de tas est effectuée. Ces structures sont organisées de manière sophistiquée pour minimiser les conflits entre les threads - ainsi certains threads peuvent partager une structure de tas (une arène), et d'autres peuvent utiliser des arènes distinctes.
Voir le fil suivant pour une excellente explication des détails: Comment malloc fonctionne-t-il dans un environnement multithread?

VladV
la source
1

En règle générale, les threads partagent le tas et d'autres ressources, mais il existe des constructions de type thread qui ne le font pas. Parmi ces constructions de type thread, on trouve les processus légers d'Erlang et les processus complets d'UNIX (créés avec un appel à fork()). Vous travaillez peut-être également sur la concurrence entre plusieurs machines, auquel cas vos options de communication inter-thread sont considérablement plus limitées.

Ken Bloom
la source
Je pensais que fork ressemblait plus à la création d'un nouveau processus qui copiait simplement les données dans un nouvel emplacement mémoire.
Jason Tholstrup
2
fork () peut servir dans de nombreux cas d'utilisation où les threads peuvent également être utilisés. En raison de la copie sur écriture, il n'y a pas de différence de coût significative sur les systèmes Unix. Le cas d'utilisation typique est celui où le travailleur est autonome (comme un serveur Web) du reste du service. Une autre possibilité est de communiquer via stdin / out avec le thread / programme principal. fork () est puissant sous Unix, alors que d'autres plates-formes comme Windows préfèrent le threading. La raison principale est probablement que l'utilisation de fork () est beaucoup plus simple et plus sûre et Unix a cette philosophie de simplicité. Voir par exemple le serveur Web Apache, avec sa lente transition vers les threads.
ypnos
1

D'une manière générale, tous les threads utilisent le même espace d'adressage et n'ont donc généralement qu'un seul tas.

Cependant, cela peut être un peu plus compliqué. Vous recherchez peut-être Thread Local Storage (TLS), mais il ne stocke que des valeurs uniques.

Spécifique à Windows: l'espace TLS peut être alloué à l'aide de TlsAlloc et libéré à l'aide de TlsFree (vue d'ensemble ici ). Encore une fois, ce n'est pas un tas, juste des DWORD.

Étrangement, Windows prend en charge plusieurs tas par processus. On peut stocker le handle du tas dans TLS. Ensuite, vous auriez quelque chose comme un "Thread-Local Heap". Cependant, seul le handle n'est pas connu des autres threads, ils peuvent toujours accéder à sa mémoire à l'aide de pointeurs car il s'agit toujours du même espace d'adressage.

EDIT : Certains allocateurs de mémoire (en particulier jemalloc sur FreeBSD) utilisent TLS pour assigner des «arènes» aux threads. Ceci est fait pour optimiser l'allocation pour plusieurs cœurs en réduisant la surcharge de synchronisation.

Meinersbur
la source
> "Etrangement, Windows prend en charge plusieurs tas par processus.", Ce n'est pas du tout bizarre, on pourrait utiliser différents tas pour différents types d'allocations, cela ajoute juste plus de flexibilité. Bien sûr, vous pouvez toujours accéder à VirtualAlloc et créer votre propre tas comme vous le souhaitez.
1

Sur le système d'exploitation FreeRTOS, les tâches (threads) partagent le même tas mais chacune d'elles a sa propre pile. Cela est très pratique lorsqu'il s'agit d'architectures de RAM à faible consommation d'énergie, car le même pool de mémoire peut être consulté / partagé par plusieurs threads, mais cela vient avec un petit problème, le développeur doit garder à l'esprit qu'un mécanisme de synchronisation de malloc et libre est nécessaire, c'est pourquoi il est nécessaire d'utiliser un certain type de synchronisation / verrouillage de processus lors de l'allocation ou de la libération de mémoire sur le tas, par exemple un sémaphore ou un mutex.

Noreddine -Kessa
la source