Cet article dit:
"une fonction peut être soit rentrante, thread-safe, les deux, ou aucun des deux."
Il dit aussi:
"Fonctions non-rentrants sont thread-dangereux".
Je peux voir comment cela peut causer une confusion. Ils signifient que les fonctions standard documentées comme n'étant pas obligées d'être re-entrantes ne doivent pas non plus être thread-safe, ce qui est vrai pour les bibliothèques POSIX iirc (et POSIX le déclare aussi pour les bibliothèques ANSI/ISO, ISO pas de concept de filetage et donc pas de concept de sécurité du fil). En d'autres termes, "si une fonction dit qu'elle n'est pas réentrante, alors elle dit que c'est aussi dangereux pour les threads". Ce n'est pas une nécessité logique, c'est juste une convention.
Voici un pseudo-code qui est thread-safe (bien, il y a beaucoup de possibilités pour les rappels de créer des interblocages dus à l'inversion de verrouillage, mais supposons que la documentation contient suffisamment d'informations pour éviter cela) .Il est censé incrémenter le compteur global, et effectuer le rappel:
take_global_lock();
int i = get_global_counter();
do_callback(i);
set_global_counter(i+1);
release_global_lock();
Si le rappel appelle à nouveau cette routine, ce qui entraîne dans un autre rappel, les deux niveaux de rappel auront le même paramètre (qui pourrait être OK, en fonction de l'API), mais le compteur ne sera incrémenté qu'une seule fois (ce qui n'est certainement pas l'API que vous voulez, il faudrait donc l'interdire).
Cela suppose que le verrou est récursif, bien sûr. Si le verrou est non-récursif, alors bien sûr le code est non-réentrant de toute façon, puisque prendre le verrou la deuxième fois ne fonctionnera pas.
Voici quelques pseudo-code qui est « faiblement rentrante » mais pas thread-safe:
int i = get_global_counter();
do_callback(i);
set_global_counter(get_global_counter()+1);
Maintenant, il est bien d'appeler la fonction du rappel, mais ce n'est pas sûr d'appeler la fonction en même temps de différents threads. Il est également dangereux de l'appeler à partir d'un gestionnaire de signal, car la ré-entrée d'un gestionnaire de signal pourrait également interrompre le comptage si le signal arrivait au bon moment. Donc, le code n'est pas réentrant dans la bonne définition.
Voici un code qui est sans doute entièrement ré-entrant (sauf que je pense que la norme distingue entre réentrant et 'non-interruptible par les signaux', et je ne sais pas où cela tombe), mais n'est toujours pas sécurité:
int i = get_global_counter();
do_callback(i);
disable_signals(); // and any other kind of interrupts on your system
set_global_counter(get_global_counter()+1);
restore_signal_state();
Sur une application mono-thread, cela est bien, en supposant que le système d'exploitation prend en charge la désactivation de tout ce qui doit être désactivé. Il empêche la ré-entrée de se produire au point critique. Selon la façon dont les signaux sont désactivés, il peut être prudent d'appeler à partir d'un gestionnaire de signal, bien que dans cet exemple particulier, le problème transmis au rappel soit toujours le même pour les appels séparés. Il peut encore aller mal multi-thread, cependant. En pratique, non thread-safe implique souvent non-re-entrant, puisque (informellement) tout ce qui peut mal tourner en raison de l'interruption du thread par l'ordonnanceur, et la fonction appelée à partir d'un autre thread, peut également ne va pas si le thread est interrompu par un signal, et la fonction est appelée à nouveau à partir du gestionnaire de signal. Mais alors le "fix" pour empêcher les signaux (les désactivant) est différent du "fix" pour empêcher la concurrence (verrous, habituellement). C'est au mieux une règle de base.
Notez que j'ai impliqué des globals ici, mais exactement les mêmes considérations s'appliqueraient si la fonction prenait comme paramètre un pointeur vers le compteur et le verrou. C'est juste que les différents cas seraient thread-unsafe ou non-re-entrant lorsqu'il est appelé avec le même paramètre, plutôt que lorsqu'il est appelé du tout.
Votre deuxième exemple ne me semble pas réapparaître. Si le changement peut être interrompu en laissant un état incohérent, et qu'un signal se produit à ce moment-là, et que le gestionnaire de ce signal appelle la fonction, alors il passe généralement en mode Boom. C'est un problème de ré-entrée, pas un problème de sécurité des threads. –
Vous avez raison - comme vous le dites ci-dessous, vous devrez également désactiver les signaux pour que le dernier exemple soit effectivement rentrant. – ConcernedOfTunbridgeWells
@ConcernedOfTunbridgeWells, si une fonction utilise un tas à l'intérieur, il y a de fortes chances que cette fonction ne soit pas réentrante. Pourquoi? – Alcott