Question

Une question assez fondamentale, mais je ne la vois pas posée nulle part.

Disons que nous avons une structure globale (en C) comme celle-ci:

struct foo {
  int written_frequently1;
  int read_only;
  int written_frequently2;
};

Il me semble évident que si nous avons beaucoup de discussions en lecture et en écriture, nous avons besoin d'un sémaphore (ou d'un autre verrou) sur les membres write_frequently , même pour la lecture, car nous ne pouvons pas être 100% sûr que les assignations à cette structure seront atomiques.

Si nous voulons que beaucoup de threads lisent le membre read_on , et aucun pour l'écrire, avons-nous besoin d'un sémaphore sur l'accès struct pour la lecture?

(J'ai tendance à dire non, car le fait que les emplacements immédiatement avant et après soient constamment modifiés ne devrait pas affecter le membre read_only , et que plusieurs threads lisant la valeur ne doivent pas interférer. les uns avec les autres, mais je ne suis pas sûr.)

[Edit: Je réalise maintenant que j'aurais dû poser cette question beaucoup mieux, afin de clarifier très précisément ce que je voulais dire. Naturellement, je n'ai pas vraiment abordé toutes les questions en jeu lorsque j'ai posé la question pour la première fois. Bien sûr, si je modifie complètement la question maintenant, je vais gâcher toutes ces bonnes réponses. Ce que je voulais dire, c'est plus comme:

struct bar {
  char written_frequently1[LONGISH_LEN];
  char read_only[LONGISH_LEN];
  char written_frequently2[LONGISH_LEN];
};

Le problème majeur que j'ai posé est celui-ci, puisque ces données font partie d'une structure, est-il influencé par les autres membres de structure et pourrait-il les influencer en retour?

Le fait que les membres aient été pensés, et donc que les écritures soient probablement atomiques, n’est en réalité qu’un fouillis rouge dans ce cas.]

Était-ce utile?

La solution 10

Un grand merci à tous les bons intervenants (et à toutes les bonnes réponses).

En résumé:

S'il existe un membre en lecture seule d'une structure (dans notre cas, si la valeur est définie une fois, bien avant qu'un thread ne veuille la lire), les threads qui lisent ce membre n'ont pas besoin de verrous, de mutex, de sémaphores ou toute autre protection contre la concurrence.

Cela est vrai même si les autres membres sont écrits fréquemment. Le fait que les différentes variables fassent partie de la même structure ne fait aucune différence.

Autres conseils

Vous avez besoin d'un mutex pour garantir qu'une opération est atomique. Donc, dans ce cas particulier, vous n’auriez peut-être pas besoin d’un mutex. Plus précisément, si chaque fil écrit dans un élément et , l’écriture est atomique. et la nouvelle valeur est indépendante de la valeur actuelle de tout élément (y compris lui-même), il n'y a pas de problème.

Exemple: chacun des threads met à jour un " last_updated_by " variable qui enregistre simplement le dernier thread qui l'a mis à jour. Clairement, tant que la variable elle-même est mise à jour de manière atomique, aucune erreur ne se produira.

Toutefois, vous avez besoin d'un mutex pour garantir la cohérence si un thread lit ou écrit plusieurs éléments à la fois, notamment parce que vous mentionnez le verrouillage d'un élément . élément plutôt que la structure entière .

Exemple: un fil de discussion met à jour le "jour", "mois". et " année " éléments d'une structure. Cela doit se produire de manière atomique, de peur qu'un autre thread ne lise la structure après le "mois". incréments mais avant le " jour " retourne à 1 pour éviter les dates telles que le 31 février. Notez que vous devez honorer le mutex lors de la lecture; sinon, vous risquez de lire une valeur erronée, à moitié mise à jour.

Si le membre read_only est en lecture seule, les données ne risquent pas d'être modifiées et la synchronisation n'est donc pas nécessaire. Il peut s’agir de données configurées avant le démarrage des threads.

Vous souhaiterez une synchronisation pour toutes les données pouvant être écrites, quelle que soit leur fréquence.

" Lecture seule " est un peu trompeur, car la variable est écrite au moins une fois lorsqu’elle est initialisée. Dans ce cas, vous avez toujours besoin d'une barrière de mémoire entre l'écriture initiale et les lectures suivantes si elles se trouvent dans des threads différents, sinon elles pourraient voir la valeur non initialisée.

Les lecteurs ont également besoin de mutex!

Il semble y avoir une idée fausse commune selon laquelle les mutex sont réservés aux écrivains et que les lecteurs n'en ont pas besoin. C'est faux, et cette idée fausse est à l'origine des bogues extrêmement difficiles à diagnostiquer.

Voici pourquoi, sous la forme d'un exemple.

Imaginez une horloge qui met à jour toutes les secondes le code suivant:

if (++seconds > 59) {        // Was the time hh:mm:59?
   seconds = 0;              // Wrap seconds..
   if (++minutes > 59)  {    // ..and increment minutes.  Was it hh:59:59?
     minutes = 0;            // Wrap minutes..
     if (++hours > 23)       // ..and increment hours.  Was it 23:59:59?
        hours = 0;           // Wrap hours.
    }
}

Si le code n'est pas protégé par un mutex, un autre thread peut lire les variables heures , minutes et secondes lorsqu'une mise à jour est en cours. En suivant le code ci-dessus:

[Start just before midnight] 23:59:59
[WRITER increments seconds]  23:59:60
[WRITER wraps seconds]       23:59:00
[WRITER increments minutes]  23:60:00
[WRITER wraps minutes]       23:00:00
[WRITER increments hours]    24:00:00
[WRITER wraps hours]         00:00:00

Le temps est invalide à partir du premier incrément jusqu'à la dernière opération six étapes plus tard . Si un lecteur vérifie l'horloge pendant cette période, il verra une valeur qui pourrait être non seulement incorrecte, mais également illégale . Et puisque votre code est dépendant de l'horloge sans afficher l'heure directement, il s'agit d'une source classique de "ricochet". erreurs qui sont notoirement difficiles à localiser.

La solution est simple.

Entourez le code de mise à jour de l'horloge d'un mutex et créez une fonction de lecteur qui verrouille également le mutex pendant son exécution. Maintenant, le lecteur attendra que la mise à jour soit terminée et le rédacteur ne changera pas les valeurs en cours de lecture.

Non.

En général, vous avez besoin de sémaphores pour empêcher l'accès simultané aux ressources (un int dans ce cas). Cependant, étant donné que le membre read_only est en lecture seule, il ne changera pas entre / pendant les accès. Notez qu'il ne doit même pas s'agir d'une lecture atomique - si rien ne change, vous êtes toujours en sécurité.

Comment définissez-vous read_only initialement?

Si tous les fils ne font que lire, vous n'avez pas besoin de sémaphore.

Vous pouvez lire ces articles sur programmation pratique sans verrou , ou simplement disséquer et comprendre les extraits fournis.

Je voudrais cacher chaque champ derrière un appel de fonction. Les champs en écriture seule auraient un sémaphore. La lecture seule ne renvoie que la valeur.

Ajout aux réponses précédentes:

  1. Dans ce cas, le paradigme naturel de la synchronisation est l’exclusion mutuelle et non les sémaphores.
  2. Je conviens que vous n'avez pas besoin de mutex sur les variables en lecture seule.
  3. Si la partie en lecture-écriture de la structure a des contraintes de cohérence, vous aurez généralement besoin de un mutex pour tous d'entre eux, afin de conserver les opérations atomiques.
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top