2008-12-09 11 views

Répondre

21

Le code rentrant n'a pas d'état en un seul point. Vous pouvez appeler le code pendant que quelque chose est en cours d'exécution dans le code. Si le code utilise l'état global, un appel peut éventuellement écraser l'état global, interrompant le calcul dans l'autre appel.

Le code de sécurité de thread est du code sans conditions de concurrence ou d'autres problèmes de concurrence. Une condition de concurrence est où l'ordre dans lequel deux threads font quelque chose affecte le calcul. Un problème de simultanéité typique est où une modification d'une structure de données partagée peut être partiellement terminée et laissée dans un état incohérent. Pour éviter cela, vous devez utiliser des mécanismes de contrôle de concurrence tels que des sémaphores de mutex pour vous assurer que rien d'autre ne peut accéder à la structure de données tant que l'opération n'est pas terminée. Par exemple, un code peut être non rentrant mais thread-safe s'il est protégé extérieurement par un mutex mais a toujours une structure de données globale où l'état doit être cohérent pendant toute la durée de l'appel. Dans ce cas, le même thread peut initier un rappel dans la procédure tout en étant protégé par un mutex externe à granularité grossière. Si le rappel a eu lieu à l'intérieur de la procédure non re-entrant, l'appel pourrait laisser la structure de données dans un état qui pourrait interrompre le calcul du point de vue de l'appelant. Un morceau de code peut être ré-entrant mais non thread-safe s'il peut faire une modification non-atomique à une structure de données partagée (et partageable) qui pourrait être interrompue au milieu de la mise à jour en laissant la structure de données dans un état inconstant. Dans ce cas, un autre thread accédant à la structure de données pourrait être affecté par la structure de données à demi-changement et soit tomber en panne ou effectuer une opération qui corrompt les données.

+3

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. –

+1

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

+0

@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

8

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.

Questions connexes