3

Je suis un peu confus quant à l'utilisation correcte des sections critiques dans les applications multithread. Dans mon application, plusieurs objets (certains tampons circulaires et un objet port série) sont partagés entre les threads. L'accès de ces objets devrait-il toujours être placé dans des sections critiques, ou seulement à certains moments? Je suspecte seulement à certains moments parce que quand j'ai essayé d'emballer chaque utilisation avec un EnterCriticalSection/LeaveCriticalSection j'ai couru dans ce qui a semblé être une condition de blocage. Toute idée que vous pourriez avoir serait appréciée. Merci.Utilisation de sections multithread et critiques - C++

Répondre

6

Si vous partagez une ressource entre threads et que certains de ces threads sont lus alors que d'autres sont en cours d'écriture, ils doivent toujours être protégés.

Il est difficile de donner plus de conseils sans en savoir plus sur votre code, mais voici quelques points généraux à garder à l'esprit.

1) Les sections critiques protègent ressources, et non processus.

2) Entrer/quitter les sections critiques dans le même ordre sur tous les threads. Si le fil A entre dans Foo, alors entre dans Bar, alors le fil B doit entrer Foo et Bar dans le même ordre. Si vous ne le faites pas, vous pourriez créer une course.

3) L'entrée et la sortie doivent être effectuées dans l'ordre inverse. Exemple, puisque vous êtes entré dans Foo et que vous avez entré Bar, vous devez quitter Bar avant de quitter Foo. Si vous ne le faites pas, vous pouvez créer un blocage.

4) Garder les serrures les plus courtes possible raisonnablement. Si vous avez terminé avec Foo avant de commencer à utiliser Bar, relâchez Foo avant d'attraper Bar. Mais vous devez toujours garder les règles de commande à l'esprit d'en haut. Dans chaque thread qui utilise à la fois Foo et Bar, vous devez acquérir et libérer dans le même ordre:

Enter Foo 
    Use Foo 
    Leave Foo 
    Enter Bar 
    Use Bar 
    Leave Bar 

5) Si vous ne lisez 99,9% du temps et écrire 0,1% du temps, ne pas essayer être intelligent. Vous devez toujours entrer le crit sec même quand vous lisez seulement. C'est parce que vous ne voulez pas qu'une écriture commence quand vous êtes au milieu d'une lecture.

6) Garder les sections critiques granulaires. Chaque section critique doit protéger une ressource et non plusieurs ressources. Si vous rendez les sections critiques trop "grandes", vous pourriez sérialiser votre application ou créer un ensemble très mystérieux d'interblocages ou de courses.

+0

Merci, John. Je vais être sûr de garder ces conseils à l'esprit, surtout pas. 6. Je pense qu'il me manque encore quelque chose, cependant. L'entrée dans une section critique empêche-t-elle les autres threads d'accéder à toutes les ressources inter-threads, ou est-ce que 'EnterCriticalSection' /' LeaveCriticalSection' effectue une magie qui empêche les autres threads d'accéder uniquement aux ressources référencées qu'ils encapsulent? Ou est-il possible d'associer une ressource à un objet 'CRITICAL_SECTION' que j'ai manqué complètement? –

+0

Il n'est pas strictement vrai que vous devez toujours acquérir un verrou lors de la lecture s'il y a des rédacteurs - il existe des structures de données et des modèles d'accès pour lesquels ce n'est pas le cas. Mais c'est une bonne règle. De plus, si le blocage des lecteurs est un goulot d'étranglement fréquent, il est important de savoir qu'il existe également des verrous de lecture-écriture qui permettent des lectures simultanées. – asveikau

+0

@asveikau: Oui, il s'agissait uniquement de règles de base appropriées pour un nouveau programmeur multithread. Aussi, à propos des verrous de lecteur-graveur (aka Gates), à moins que les lectures aient une longue durée de vie, il est souvent plus lent d'utiliser un Gate que de simplement utiliser un crit-sec. Voir cet article pour plus d'informations: http://queue.acm.org/detail.cfm?id=1454462 –

2

Utilisez un wrapper C++ autour de la section critique qui soutient RAII:

{ 
    CriticalSectionLock lock (mutex_); 

    Do stuff... 
} 

Le constructeur de la serrure acquiert le mutex et les destructor libère le mutex même si une exception est levée. Essayez de ne pas gagner plus d'un verrou à la fois et essayez d'éviter d'appeler des fonctions en dehors de votre classe tout en maintenant des verrous; Cela évite d'avoir à gagner des verrous à différents endroits, vous avez donc moins de possibilités de blocage.

Si vous devez gagner plus d'un verrou en même temps, triez les verrous par leur adresse et récupérez-les dans l'ordre. De cette façon, plusieurs processus gagnent les mêmes verrous dans le même ordre sans coordination. Dans le cas d'un port d'E/S, déterminez si vous devez verrouiller la sortie en même temps que l'entrée - souvent vous avez un cas où quelque chose tente d'écrire, puis attend de lire, ou vice-versa. Si vous avez deux verrous, vous pouvez obtenir un blocage si un thread écrit puis lit, et l'autre lit puis écrit.Souvent, avoir un thread qui fait l'IO et une file d'attente de requêtes résout cela, mais c'est un peu plus compliqué que de simplement encapsuler des appels avec des verrous, et sans beaucoup plus de détails je ne peux pas le recommander.

+0

+1 pour éviter les fonctions d'appel en dehors de votre classe. Je voudrais prendre cela au niveau suivant et dire qu'en général vous devriez éviter d'appeler le code de la bibliothèque autant que possible tout en maintenant un verrou. (Sauf si vous avez une très bonne raison.) – Marcin