web-dev-qa-db-fra.com

Threadsafe vs rentrant

Récemment, j'ai posé une question, dont le titre est "Le fil malloc est-il sûr?" , et à l'intérieur de cela j'ai demandé: "Le malloc est-il rentrant?"

J'avais l'impression que tous les rentrants sont thread-safe.

Cette hypothèse est-elle fausse?

82
Alphaneo

Les fonctions réentrantes ne reposent pas sur des variables globales qui sont exposées dans les en-têtes de la bibliothèque C. Prenez par exemple strtok () vs strtok_r () en C.

Certaines fonctions ont besoin d'un emplacement 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. Ce stockage étant exclusif à la fonction appelante, il peut être interrompu et ressaisi (rentrant) et puisque dans la plupart des cas l'exclusion mutuelle au-delà de ce la fonction implémente n'est pas requise pour que cela fonctionne, ils sont souvent considérés comme 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 l'idiot dans toute explication de la façon dont tout cela fonctionne) :)

En bref, réentrant souvent signifie thread-safe (comme dans "utiliser la version réentrante de cette fonction si vous utilisez des threads"), mais thread safe ne 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, alors la fonction n'est pas intrinsèquement thread-safe.

Mais, toutes les fonctions ne doivent pas non plus être examinées. 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écuritaires 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 s'ils ne vont 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 une plate-forme intégrée.

Dans la programmation purement fonctionnelle, réentrant souvent n'implique pas un thread-safe, cela dépendra du comportement des fonctions définies ou anonymes passées au point d'entrée de la fonction, récursivité, etc.

Une meilleure façon de mettre 'thread safe' est sûr pour un accès simultané , ce qui illustre mieux le besoin.

40
Tim Post

TL; DR: Une fonction peut être réentrante, thread-safe, les deux ou aucun.

Les articles Wikipédia pour thread-safety et reentrancy valent la peine d'être lus. Voici quelques citations:

Une fonction est thread-safe si:

il manipule uniquement les structures de données partagées d'une manière qui garantit 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 de nouveau appelé en toute sécurité ("rentré") avant que ses appels précédents aient terminé leur exécution.

À titre d'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 s'exécute déjà 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éentrée 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 se réfère à un seul thread en cours d'exécution. C'est un concept de l'époque où aucun système d'exploitation multitâche n'existait.

Exemples

(Légèrement modifié à partir des articles Wikipedia)

Exemple 1: pas thread-safe, pas rentrant

/* 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: pas thread-safe, rentrant

/* 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, rentrant

/* 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;
}
67
MiniQuark

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 invocation utilise ses propres données.

Par conséquent, une fonction thread-safe est toujours réentrante, mais une fonction reentrant 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 dans le domaine du multithreading n'est pas entièrement normalisé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.

55
Georg Schölly