Récemment, j'ai posé une question, dont le titre était "Le fil malloc est-il sûr?" , et à l'intérieur, j'ai demandé: "Malloc est-il réentrant?"
J'avais l'impression que tous les rentrants sont thread-safe.
Cette hypothèse est-elle fausse?
Récemment, j'ai posé une question, dont le titre était "Le fil malloc est-il sûr?" , et à l'intérieur, j'ai demandé: "Malloc est-il réentrant?"
J'avais l'impression que tous les rentrants sont thread-safe.
Cette hypothèse est-elle fausse?
Réponses:
Les fonctions rentrantes ne reposent pas sur des variables globales qui sont exposées dans les en-têtes de la bibliothèque C .. prenez strtok () vs strtok_r () par exemple en C.
Certaines fonctions ont besoin d'un endroit pour stocker un 'travail en cours', les fonctions réentrantes vous permettent de spécifier ce pointeur dans le propre stockage du thread, pas dans un global. Étant donné que ce stockage est exclusif à la fonction appelante, il peut être interrompu et réintégré (rentrant) et comme dans la plupart des cas, l'exclusion mutuelle au-delà de ce que la fonction implémente n'est pas nécessaire pour que cela fonctionne, ils sont souvent considérés comme étant thread safe . Ce n'est cependant pas garanti par définition.
errno, cependant, est un cas légèrement différent sur les systèmes POSIX (et a tendance à être étrange dans toute explication de la façon dont tout cela fonctionne) :)
En bref, réentrant signifie souvent thread-safe (comme dans «utiliser la version réentrante de cette fonction si vous utilisez des threads»), mais thread safe ne signifie pas toujours rentrant (ou l'inverse). Lorsque vous regardez la sécurité des threads, la concurrence est ce à quoi vous devez penser. Si vous devez fournir un moyen de verrouillage et d'exclusion mutuelle pour utiliser une fonction, la fonction n'est pas intrinsèquement thread-safe.
Mais toutes les fonctions n'ont pas besoin d'être examinées pour l'un ou l'autre. malloc()
n'a pas besoin d'être réentrant, il ne dépend de rien hors de la portée du point d'entrée pour un thread donné (et est lui-même thread-safe).
Les fonctions qui renvoient des valeurs allouées statiquement ne sont pas sûres pour les threads sans l'utilisation d'un mutex, d'un futex ou d'un autre mécanisme de verrouillage atomique. Pourtant, ils n'ont pas besoin d'être réentrants pour ne pas être interrompus.
c'est à dire:
static char *foo(unsigned int flags)
{
static char ret[2] = { 0 };
if (flags & FOO_BAR)
ret[0] = 'c';
else if (flags & BAR_FOO)
ret[0] = 'd';
else
ret[0] = 'e';
ret[1] = 'A';
return ret;
}
Donc, comme vous pouvez le voir, avoir plusieurs threads utiliser cela sans une sorte de verrouillage serait un désastre ... mais cela n'a aucun but d'être rentrant. Vous rencontrerez cela lorsque la mémoire allouée dynamiquement est taboue sur certaines plates-formes intégrées.
Dans la programmation purement fonctionnelle, réentrant n'implique souvent pas de thread safe, cela dépendrait du comportement de fonctions définies ou anonymes passées au point d'entrée de la fonction, de la récursivité, etc.
Une meilleure façon de mettre «thread safe» est sans danger pour l'accès simultané , ce qui illustre mieux le besoin.
TL; DR: Une fonction peut être réentrante, thread-safe, les deux ou aucun.
Les articles de Wikipédia sur la sécurité des threads et la réentrance méritent d'être lus. Voici quelques citations:
Une fonction est thread-safe si:
il ne manipule que les structures de données partagées de manière à garantir une exécution sûre par plusieurs threads en même temps.
Une fonction est réentrante si:
il peut être interrompu à tout moment au cours de son exécution, puis rappelé en toute sécurité ("ressaisi") avant que ses appels précédents ne terminent l'exécution.
Comme exemples de réentrance possible, Wikipédia donne l'exemple d'une fonction conçue pour être appelée par des interruptions du système: supposons qu'elle soit déjà en cours d'exécution lorsqu'une autre interruption se produit. Mais ne pensez pas que vous êtes en sécurité simplement parce que vous ne codez pas avec des interruptions système: vous pouvez avoir des problèmes de réentrance dans un programme à un seul thread si vous utilisez des rappels ou des fonctions récursives.
La clé pour éviter toute confusion est que réentrant fait référence à un seul thread en cours d'exécution. C'est un concept de l'époque où il n'existait aucun système d'exploitation multitâche.
Exemples
(Légèrement modifié à partir des articles de Wikipedia)
Exemple 1: pas thread-safe, pas réentrant
/* As this function uses a non-const global variable without
any precaution, it is neither reentrant nor thread-safe. */
int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
*y = t;
}
Exemple 2: thread-safe, non réentrant
/* We use a thread local variable: the function is now
thread-safe but still not reentrant (within the
same thread). */
__thread int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
*y = t;
}
Exemple 3: non thread-safe, réentrant
/* We save the global state in a local variable and we restore
it at the end of the function. The function is now reentrant
but it is not thread safe. */
int t;
void swap(int *x, int *y)
{
int s;
s = t;
t = *x;
*x = *y;
*y = t;
t = s;
}
Exemple 4: thread-safe, réentrant
/* We use a local variable: the function is now
thread-safe and reentrant, we have ascended to
higher plane of existence. */
void swap(int *x, int *y)
{
int t;
t = *x;
*x = *y;
*y = t;
}
t = *x
, appelle swap()
, alors t
sera surchargé, conduisant à des résultats inattendus.
swap(5, 6)
est interrompu par un swap(1, 2)
. Après t=*x
, s=t_original
et t=5
. Maintenant, après l'interruption, s=5
et t=1
. Cependant, avant le second swap
retour, il restaurera le contexte, créant t=s=5
. Maintenant, nous revenons au premier swap
avec t=5 and s=t_original
et continuons après t=*x
. Ainsi, la fonction semble être rentrante. N'oubliez pas que chaque appel obtient sa propre copie d' s
allocation sur pile.
Cela dépend de la définition. Par exemple, Qt utilise ce qui suit:
Une fonction thread-safe * peut être appelée simultanément à partir de plusieurs threads, même lorsque les appels utilisent des données partagées, car toutes les références aux données partagées sont sérialisées.
Une fonction réentrante peut également être appelée simultanément à partir de plusieurs threads, mais uniquement si chaque appel utilise ses propres données.
Par conséquent, une fonction thread-safe est toujours réentrante, mais une fonction réentrante n'est pas toujours thread-safe.
Par extension, une classe est dite réentrante si ses fonctions membres peuvent être appelées en toute sécurité à partir de plusieurs threads, tant que chaque thread utilise une instance différente de la classe. La classe est thread-safe si ses fonctions membres peuvent être appelées en toute sécurité à partir de plusieurs threads, même si tous les threads utilisent la même instance de la classe.
mais ils mettent également en garde:
Remarque: la terminologie du domaine multithreading n'est pas entièrement standardisée. POSIX utilise des définitions de réentrant et de thread-safe qui sont quelque peu différentes pour ses API C. Lorsque vous utilisez d'autres bibliothèques de classes C ++ orientées objet avec Qt, assurez-vous que les définitions sont comprises.