web-dev-qa-db-fra.com

Existe-t-il un modèle de conception C ++ qui implémente un mécanisme ou un mutex qui contrôle la durée pendant laquelle un thread peut posséder une ressource verrouillée?

Je cherche un moyen de garantir que chaque fois qu'un thread verrouille une ressource spécifique, il est obligé de libérer cette ressource après une période de temps spécifique (s'il ne l'a pas déjà publiée). Imaginez une connexion où vous devez limiter la durée pendant laquelle un thread spécifique peut posséder cette connexion.

J'imagine que c'est ainsi qu'il pourrait être utilisé:

{
    std::lock_guard<std::TimeLimitedMutex> lock(this->myTimeLimitedMutex, timeout);
    try {
        // perform some operation with the resource that myTimeLimitedMutex guards. 
    }
    catch (MutexTimeoutException ex) {
        // perform cleanup
    }
}

Je vois qu'il y a un timed_mutex qui laisse le programme expirer si un verrou ne peut pas être acquis. J'ai besoin du délai d'expiration après le verrou est acquis.

Il existe déjà des situations où vous obtenez une ressource qui peut être supprimée de façon inattendue. Par exemple, un socket TCP - une fois la connexion socket établie, le code de chaque côté doit gérer le cas où l'autre côté abandonne la connexion.

Je recherche un modèle qui gère les types de ressources qui expirent normalement d'eux-mêmes, mais lorsqu'ils ne le font pas, ils doivent être réinitialisés. Cela ne doit pas gérer tous les types de ressources.

16
Jay Elston

Cela ne peut pas fonctionner et cela ne fonctionnera jamais. En d'autres termes, cela ne peut jamais être fait. Cela va à l'encontre de tout concept de propriété et de transactions atomiques. Parce que lorsque thread acquiert le verrou et implémente deux transactions d'affilée, il s'attend à ce qu'elles deviennent atomiquement visibles à l'extérieur de Word. Dans ce scénario, il serait très possible que la transaction soit déchirée - la première partie sera effectuée, mais pas la seconde.

Ce qui est pire, c'est que puisque le verrou sera supprimé de force, la transaction partiellement exécutée deviendra visible à l'extérieur de Word, avant que le thread interrompu ait la moindre chance de revenir en arrière.

Cette idée va à l'encontre de toute école de pensée multithread.

32
SergeyA

Je soutiens la réponse de SergeyAs. La libération d'un mutex verrouillé après une temporisation est une mauvaise idée et ne peut pas fonctionner. Mutex est synonyme d'exclusion mutuelle et il s'agit d'un contrat extrêmement difficile qui ne peut être violé.

Mais vous pouvez faire ce que vous voulez:

Problème: Vous voulez garantir que vos threads ne tiennent pas le mutex plus longtemps qu'un certain temps T.

Solution: Ne verrouillez jamais le mutex plus longtemps que le temps T. Au lieu de cela, écrivez votre code afin que le mutex ne soit verrouillé que pour les opérations absolument nécessaires. Il est toujours possible de donner un tel temps T (modulo les incertitudes et limites étant donné mon système d'exploitation multitâche et multi-utilisateur bien sûr).

Pour y parvenir (exemples):

  • Ne déposez jamais d'E/S dans une section verrouillée.
  • N'appelez jamais un appel système pendant qu'un mutex est verrouillé.
  • Évitez de trier une liste pendant qu'un mutex est verrouillé (*).
  • Évitez de faire une opération lente sur chaque élément d'une liste pendant qu'un mutex est verrouillé (*).
  • Évitez l'allocation/désallocation de mémoire pendant qu'un mutex est verrouillé (*).

Il existe des exceptions à ces règles, mais la ligne directrice générale est la suivante:

  • Rendez votre code légèrement moins optimal (par exemple, effectuez une copie redondante à l'intérieur de la section critique) pour rendre la section critique aussi courte que possible. C'est une bonne programmation multithreading.

(*) Ce ne sont que des exemples d'opérations où il est tentant de verrouiller la liste entière, d'effectuer les opérations puis de déverrouiller la liste. Au lieu de cela, il est conseillé de simplement prendre une copie locale de la liste et d'effacer la liste d'origine pendant que le mutex est verrouillé, idéalement en utilisant l'opération swap() offerte par la plupart des conteneurs STL. Effectuez ensuite l'opération lente sur la copie locale en dehors de la section critique. Ce n'est pas toujours possible mais vaut toujours la peine d'être considéré. Le tri a une complexité carrée dans le pire des cas et nécessite généralement un accès aléatoire à la liste entière. Il est utile de trier (une copie de) la liste en dehors de la section critique et de vérifier ultérieurement si des éléments doivent être ajoutés ou supprimés. Les allocations de mémoire ont également une certaine complexité derrière elles, donc les allocations/désallocations massives de mémoire doivent être évitées.

15
Johannes Overmann

Vous ne pouvez pas faire cela avec seulement C++.

Si vous utilisez un système Posix, cela peut être fait. Vous devrez déclencher un signal SIGALARM qui n'est démasqué que pour le thread qui va expirer. Dans le gestionnaire de signaux, vous devrez définir un indicateur et utiliser longjmp pour revenir au code de thread. Dans le code de thread, à la position setjmp, vous ne pouvez être appelé que si le signal a été déclenché, vous pouvez donc lever l'exception Timeout.

Veuillez voir cette réponse pour savoir comment procéder.

De plus, sous linux, il semble vous pouvez lancer directement depuis le gestionnaire de signal (donc pas de longjmp/setjmp ici).

BTW, si j'étais vous, je coderais le contraire. Pensez-y: vous voulez dire à un fil "hé, vous prenez trop de temps, alors jetons tout le (long) travail que vous avez fait jusqu'à présent pour que je puisse progresser". Idéalement, votre long fil devrait être plus coopératif, faire quelque chose comme "J'ai fait A d'une tâche ABCD, libérons le mutex pour que d'autres puissent progresser sur A. Ensuite, vérifions si je peux le reprendre pour faire B et bientôt." Vous voulez probablement être plus fin (avoir plus de mutex sur des objets plus petits, mais assurez-vous de verrouiller dans le même ordre) ou utiliser des verrous RW (afin que d'autres threads puissent utiliser les objets si vous ne les modifiez pas), etc...

5
xryl669

Une telle approche ne peut pas être appliquée car le titulaire du mutex doit avoir la possibilité de nettoyer tout ce qui est laissé dans un état non valide pendant la transaction. Cela peut prendre un temps arbitraire inconnu.

L'approche typique consiste à libérer le verrou lorsque vous effectuez de longues tâches et à le réacquérir au besoin. Vous devez gérer cela vous-même car tout le monde aura une approche légèrement différente.

La seule situation que je connaisse où ce genre de chose est une pratique acceptée est au niveau du noyau, en particulier en ce qui concerne les microcontrôleurs (qui n'ont pas de noyau, ou sont tous du noyau, selon la personne à qui vous demandez). Vous pouvez définir une interruption qui modifie la pile d'appels, de sorte que lorsqu'elle est déclenchée, elle déroule les opérations particulières qui vous intéressent.

1
Cort Ammon

Les variables "condition" peuvent avoir des délais d'expiration. Cela vous permet d'attendre qu'un thread volontairement libère une ressource (avec notify_one () ou notify_all ()), mais l'attente elle-même expirera après un délai spécifié durée fixe.

Des exemples dans la documentation de Boost pour les "conditions" pourraient rendre cela plus clair.

Si vous voulez forcer une version, vous devez cependant écrire le code qui la forcera. Cela pourrait être dangereux. Le code écrit en C++ peut faire des trucs assez proches du métal. La ressource pourrait accéder à du matériel réel et attendre qu'elle finisse quelque chose. Il peut ne pas être physiquement possible de mettre fin à tout ce que le programme est bloqué.

Cependant, si cela est possible, vous pouvez le gérer dans le thread dans lequel l'attente () expire.

0
Dmitry Rubanovich