Aller au contenu principal

L'interblocage

Ce que ce chapitre apporte

  • Reconnaître une attente circulaire entre deux fils, et dire ce qui la distingue d'une attente ordinaire.
  • Lire une figure qui annonce l'interblocage, et identifier l'instruction sur laquelle chaque fil est resté.
  • Appliquer la règle qui dissout l'attente circulaire : acquérir les verrous dans un ordre identique partout.
  • Expliquer pourquoi ce défaut échappe à la relecture comme aux tests.
  • Distinguer l'interblocage de la famine, et savoir laquelle des deux finit par se débloquer.

Le chapitre sur le verrou s'est terminé sur une question laissée en suspens : que se passe-t-il quand un fil a besoin de deux verrous à la fois ? La réponse est le défaut le plus déroutant de tout le module. Le programme qui suit est syntaxiquement irréprochable, chaque verrou y est pris et libéré comme il se doit, chaque section critique y est correctement délimitée, et pourtant la ligne de production s'arrête, définitivement, dans un entrelacement sur trois. Rien dans le code ne peut être montré du doigt : la faute est dans l'ordre des acquisitions, et un ordre ne se lit pas sur une ligne.

Deux ressources pour une seule opération

Une cellule d'assemblage comporte un convoyeur et un bras robotique. Transférer une pièce demande les deux en même temps : le convoyeur doit rester immobile pendant que le bras saisit la pièce. Deux lignes de production partagent cette cellule, et chacune réserve ce dont elle a besoin par un verrou.

La ligne A réserve le convoyeur puis le bras, ce qui est l'ordre naturel de son opération : la pièce arrive par le convoyeur, le bras la reprend. La ligne B fait l'inverse, tout aussi naturellement : le bras dépose la pièce, le convoyeur l'emporte. Chacune prend ses deux verrous, s'en sert, et les libère. Chaque programme, pris isolément, est juste.

2 fils d'exécutionavancer l'un ou l'autre, dans l'ordre voulu
mémoire partagéetransferees = 0convoyeur : librebras : libre

fil ligne-A

registre vide
  1. verrouiller convoyeur
  2. verrouiller bras
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer bras
  7. libérer convoyeur

fil ligne-B

registre vide
  1. verrouiller bras
  2. verrouiller convoyeur
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer convoyeur
  7. libérer bras

Avancer un fil, puis l'autre, dans l'ordre voulu. Un fil peut aussi être avancé plusieurs fois de suite : c'est ce que fait l'ordonnanceur quand il ne l'interrompt pas.

Avancer ligne-A d'un pas, puis ligne-B d'un pas, puis essayer l'un et l'autre. Lire la mention que porte alors la figure, puis recommencer en menant ligne-A jusqu'au bout avant de toucher à ligne-B.

Deux clics suffisent. La ligne A détient le convoyeur et demande le bras ; la ligne B détient le bras et demande le convoyeur. Aucun bouton n'accepte plus le clic, aucune des deux ne relâchera quoi que ce soit, puisque chacune attend avant la partie de son programme qui libère. La figure l'annonce en toutes lettres : interblocage. Le compteur de pièces transférées est resté à 0, et il y restera.

Sur le terrain, cela s'appelle une ligne arrêtée. Aucun voyant ne s'allume, aucune alarme ne se déclenche, les deux automates sont sous tension et « en cours de traitement ». Le diagnostic prend des heures, parce que la première question posée est toujours « qu'est-ce qui a planté ? », et que rien n'a planté.

Définition

Un interblocage est un état où un groupe de fils s'attendent mutuellement, chacun détenant une ressource que réclame un autre, de sorte qu'aucun ne peut plus progresser. La figure le détecte à ce critère exact : les fils n'ont pas tous terminé, et aucun de ceux qui restent ne peut avancer.

L'attente circulaire en est la cause : la ligne A attend le bras, que détient B, qui attend le convoyeur, que détient A. Tracer les attentes forme un cycle, et un cycle ne se résout jamais de lui-même.

Un code juste, qui ne s'arrête jamais

Le point le plus important du chapitre est celui-ci, et il mérite d'être martelé : ce programme n'a aucune erreur de programmation au sens habituel. Il compile, il passe l'analyse statique, chaque fonction prise séparément est correcte, les verrous sont libérés dans un bloc with, et une relecture ligne à ligne ne révèle rien, parce qu'il n'y a rien à voir sur une ligne. Le défaut est une propriété de deux morceaux de code mis ensemble, et il porte sur l'ordre dans lequel ils réclament leurs ressources.

Restent les tests. Ils ne le trouvent pas davantage, et la figure explique pourquoi mieux qu'un discours.

2 fils d'exécutionavancer l'un ou l'autre, dans l'ordre voulu
mémoire partagéetransferees = 0convoyeur : librebras : libre

fil ligne-A

registre vide
  1. verrouiller convoyeur
  2. verrouiller bras
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer bras
  7. libérer convoyeur
  8. afficher « A a transféré sa pièce »

fil ligne-B

registre vide
  1. verrouiller bras
  2. verrouiller convoyeur
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer convoyeur
  7. libérer bras
  8. afficher « B a transféré sa pièce »

Avancer un fil, puis l'autre, dans l'ordre voulu. Un fil peut aussi être avancé plusieurs fois de suite : c'est ce que fait l'ordonnanceur quand il ne l'interrompt pas.

Mener ligne-A entièrement avant de lancer ligne-B, et constater que tout se passe bien. Demander ensuite le compte des entrelacements et lire combien laissent le compteur à 0.

C'est le même programme, avec une ligne d'affichage de plus dans chaque fil. Le compte donne trente-six entrelacements, dont deux finissent en interblocage. Sur la figure précédente, plus courte, c'étaient deux entrelacements sur six.

Le nombre d'entrelacements dangereux n'a pas bougé, c'est le nombre total qui a été multiplié par six. Autrement dit, plus le programme contient de travail entre les acquisitions, plus l'interblocage devient rare, sans jamais devenir impossible. Un test qui lance la cellule cent fois a de bonnes chances de ne rien voir. Le blocage attendra la charge réelle, celle où les deux lignes tournent ensemble des milliers de fois par jour, et il arrivera.

La nature de la faute

Un interblocage n'est pas une erreur de calcul, c'est une erreur de protocole d'acquisition. Elle ne se corrige donc pas dans une fonction, mais dans une convention qui s'applique à tout le programme, et qui doit être écrite quelque part pour survivre au départ de celui qui l'a établie.

La règle qui dissout l'attente circulaire

L'attente circulaire a besoin d'un cycle. Supprimer le cycle supprime l'interblocage, et il existe une façon de le supprimer qui ne coûte rien : décider un ordre unique sur tous les verrous du programme, et exiger que chaque fil acquière les siens dans cet ordre.

Avec l'ordre « convoyeur avant bras », la ligne B doit réclamer le convoyeur en premier, même si son opération commence par le bras. Rien d'autre ne change.

2 fils d'exécutionavancer l'un ou l'autre, dans l'ordre voulu
mémoire partagéetransferees = 0convoyeur : librebras : libre

fil ligne-A

registre vide
  1. verrouiller convoyeur
  2. verrouiller bras
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer bras
  7. libérer convoyeur

fil ligne-B

registre vide
  1. verrouiller convoyeur
  2. verrouiller bras
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer bras
  7. libérer convoyeur

Avancer un fil, puis l'autre, dans l'ordre voulu. Un fil peut aussi être avancé plusieurs fois de suite : c'est ce que fait l'ordonnanceur quand il ne l'interrompt pas.

Chercher un entrelacement qui fige la cellule, en essayant les deux fils dans tous les ordres. Demander ensuite le compte des entrelacements, et vérifier qu'aucun ne laisse le compteur à 0.

Deux entrelacements, aucun interblocage, et le compteur finit à 2 dans les deux cas. La raison tient en une phrase : celui qui obtient le premier verrou obtiendra le second, puisque personne ne peut détenir le second sans avoir obtenu le premier. Le cycle est devenu impossible à former, et il l'est par construction, pas par chance.

À retenir

Acquérir les verrous dans un ordre identique partout. L'ordre lui-même n'a aucune importance, seule compte son unicité. Il peut suivre l'ordre alphabétique des noms de ressources, un identifiant d'équipement, l'ordre de déclaration dans le programme : ce qui protège n'est pas le choix, c'est le fait que tout le code s'y tienne.

L'ordre de libération, lui, est indifférent : libérer ne fait attendre personne.

Trois postes, trois verrous, et un cycle plus difficile à voir

À deux fils, le cycle se repère en lisant deux lignes côte à côte. À trois, plus personne ne le voit, parce qu'aucune paire de fils prise isolément n'est fautive. Une cellule complète comporte un convoyeur, un bras et une palette ; chaque poste n'a besoin que de deux ressources sur les trois.

3 fils d'exécutionavancer l'un ou l'autre, dans l'ordre voulu
mémoire partagéetransferees = 0convoyeur : librebras : librepalette : libre

fil poste-A

registre vide
  1. verrouiller convoyeur
  2. verrouiller bras
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer bras
  7. libérer convoyeur

fil poste-B

registre vide
  1. verrouiller bras
  2. verrouiller palette
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer palette
  7. libérer bras

fil poste-C

registre vide
  1. verrouiller palette
  2. verrouiller convoyeur
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer convoyeur
  7. libérer palette

Avancer un fil, puis l'autre, dans l'ordre voulu. Un fil peut aussi être avancé plusieurs fois de suite : c'est ce que fait l'ordonnanceur quand il ne l'interrompt pas.

Avancer chaque poste d'un seul pas, dans l'ordre A, B, C, puis essayer de faire avancer n'importe lequel. Demander le compte des entrelacements et lire la part de ceux qui laissent le compteur à 0.

Trois clics, un par poste, et la cellule est figée : A tient le convoyeur et veut le bras, B tient le bras et veut la palette, C tient la palette et veut le convoyeur. Aucune paire ne suffit à expliquer le blocage, il faut les trois.

Le compte donne quatre cent deux entrelacements, dont six finissent en interblocage, soit un peu moins de un et demi pour cent. Un essai sur soixante-sept, voilà la fréquence d'un défaut qui arrête une ligne de production. Aucune campagne de tests ne garantit de le rencontrer, et son absence ne prouve rien.

La règle ne change pas d'un iota, et elle traite le cas à trois aussi bien que le cas à deux. Ordre retenu ici : bras, puis convoyeur, puis palette. Chaque poste réclame ce dont il a besoin dans cet ordre, en sautant les verrous qui ne le concernent pas.

3 fils d'exécutionavancer l'un ou l'autre, dans l'ordre voulu
mémoire partagéetransferees = 0convoyeur : librebras : librepalette : libre

fil poste-A

registre vide
  1. verrouiller bras
  2. verrouiller convoyeur
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer convoyeur
  7. libérer bras

fil poste-B

registre vide
  1. verrouiller bras
  2. verrouiller palette
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer palette
  7. libérer bras

fil poste-C

registre vide
  1. verrouiller convoyeur
  2. verrouiller palette
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer palette
  7. libérer convoyeur

Avancer un fil, puis l'autre, dans l'ordre voulu. Un fil peut aussi être avancé plusieurs fois de suite : c'est ce que fait l'ordonnanceur quand il ne l'interrompt pas.

Rejouer l'entrelacement qui figeait la cellule, un pas par poste dans l'ordre A, B, C. Demander le compte des entrelacements et vérifier que le compteur finit toujours à 3.

Cinq cent trente-huit entrelacements, aucun interblocage, et le compteur finit à 3 dans tous les cas. Deux postes ont changé : A prend désormais le bras avant le convoyeur, et C le convoyeur avant la palette, l'un et l'autre à rebours de leur logique métier. C'est le prix à payer, et il est nul.

La famine

L'interblocage n'est pas la seule façon de ne jamais avancer. Un fil peut aussi rester correct, non bloqué au sens de la figure, et pourtant ne jamais obtenir ce qu'il demande, simplement parce que d'autres le reprennent plus vite que lui.

2 fils d'exécutionavancer l'un ou l'autre, dans l'ordre voulu
mémoire partagéelignes = 0journal : libre

fil rapide

registre vide
  1. verrouiller journal
  2. lire lignes
  3. ajouter +1
  4. écrire lignes
  5. libérer journal
  6. verrouiller journal
  7. lire lignes
  8. ajouter +1
  9. écrire lignes
  10. libérer journal
  11. verrouiller journal
  12. lire lignes
  13. ajouter +1
  14. écrire lignes
  15. libérer journal

fil lent

registre vide
  1. verrouiller journal
  2. lire lignes
  3. ajouter +1
  4. écrire lignes
  5. libérer journal

Avancer un fil, puis l'autre, dans l'ordre voulu. Un fil peut aussi être avancé plusieurs fois de suite : c'est ce que fait l'ordonnanceur quand il ne l'interrompt pas.

Faire demander le verrou à lent dès le premier pas, puis avancer systématiquement rapide à chaque libération. Compter combien de fois lent voit son bouton se refuser avant de passer enfin.

Le fil lent demande le verrou en premier et passe en dernier. À chaque libération, rien n'oblige le système à le servir : si le fil rapide redemande aussitôt, il peut très bien l'obtenir de nouveau, et le lent attend un tour de plus. Ici le programme finit, parce que le fil rapide n'a que trois passages à faire ; dans un programme qui tourne en continu, il n'en a pas trois mais un flot, et le fil lent peut attendre indéfiniment.

Définition

La famine est la situation d'un fil qui pourrait progresser mais n'obtient jamais la ressource qu'il demande, parce que d'autres fils la reprennent sans cesse avant lui. Elle se distingue de l'interblocage sur un point décisif : rien n'est figé, le programme avance, et le fil affamé finira peut-être par passer. Un interblocage, lui, ne se dénoue jamais.

Un verrou ordinaire ne promet rien sur l'ordre de service. Les remèdes relèvent de la conception : réduire la durée de détention pour que les passages soient plus nombreux, employer une file d'attente qui sert dans l'ordre d'arrivée quand la plateforme en offre une, ou revoir un découpage qui fait passer un même fil cent fois plus souvent que les autres.

Les gestes Python

Deux points pratiques accompagnent la règle d'ordre. Le premier est un piège qui produit un interblocage à un seul fil : un verrou ordinaire pris deux fois par le même fil se bloque lui-même, car il ne compte pas ses acquisitions.

main.py
Sortie
>_ Prêt à exécuter…

La seconde acquisition d'un Lock échoue, et elle aurait bloqué le fil pour toujours si elle avait été demandée normalement. Le cas se produit dès qu'une fonction protégée en appelle une autre qui prend le même verrou, ce qui arrive sans que personne ne l'ait voulu, après une réorganisation du code. Le RLock accepte la reprise par son détenteur et demande autant de libérations que d'acquisitions.

Le second point est la mise en œuvre de l'ordre unique. Quand les verrous sont connus d'avance, il suffit de les écrire dans le bon ordre. Quand ils sont créés dynamiquement, un par machine ou un par dossier, l'ordre se définit sur une clé stable, et l'acquisition passe par une pile de contextes.

main.py
Sortie
>_ Prêt à exécuter…

L'appelant demande ses ressources dans l'ordre qui l'arrange, sorted impose l'ordre commun, et ExitStack libère tout à la sortie du bloc, y compris si une exception traverse le traitement. L'ordre d'acquisition cesse alors de dépendre de la vigilance du rédacteur, ce qui est la seule façon d'en faire une règle tenable.

Exercices type

Le poste C ci-dessous a été écrit après les deux autres, par quelqu'un qui appliquait l'ordre « bras avant convoyeur avant palette ». La cellule se fige pourtant. Chercher ce qui viole la convention, puis rétablir l'ordre partout où il manque.
3 fils d'exécutionavancer l'un ou l'autre, dans l'ordre voulu
mémoire partagéetransferees = 0convoyeur : librebras : librepalette : libre

fil poste-A

registre vide
  1. verrouiller bras
  2. verrouiller palette
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer palette
  7. libérer bras

fil poste-B

registre vide
  1. verrouiller palette
  2. verrouiller convoyeur
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer convoyeur
  7. libérer palette

fil poste-C

registre vide
  1. verrouiller convoyeur
  2. verrouiller bras
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer bras
  7. libérer convoyeur

Avancer un fil, puis l'autre, dans l'ordre voulu. Un fil peut aussi être avancé plusieurs fois de suite : c'est ce que fait l'ordonnanceur quand il ne l'interrompt pas.

Avancer chaque poste d'un pas dans l'ordre A, B, C et relever quel verrou chaque poste détient et lequel il réclame. Chercher ensuite le poste dont l'ordre viole la convention.

Le poste A respecte la convention : il prend le bras, puis la palette. Le poste C ne la respecte pas, puisqu'il prend le convoyeur avant le bras. Mais le poste B ne la respecte pas non plus : il prend la palette avant le convoyeur, et l'ordre impose bras, convoyeur, palette.

Deux postes sont donc fautifs, et c'est le premier enseignement de l'exercice : une convention d'ordre se vérifie poste par poste, jamais par paires. Corriger le seul poste C ne suffirait pas.

La correction rétablit l'ordre partout, sans rien changer d'autre.

3 fils d'exécutionavancer l'un ou l'autre, dans l'ordre voulu
mémoire partagéetransferees = 0convoyeur : librebras : librepalette : libre

fil poste-A

registre vide
  1. verrouiller bras
  2. verrouiller palette
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer palette
  7. libérer bras

fil poste-B

registre vide
  1. verrouiller convoyeur
  2. verrouiller palette
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer palette
  7. libérer convoyeur

fil poste-C

registre vide
  1. verrouiller bras
  2. verrouiller convoyeur
  3. lire transferees
  4. ajouter +1
  5. écrire transferees
  6. libérer convoyeur
  7. libérer bras

Avancer un fil, puis l'autre, dans l'ordre voulu. Un fil peut aussi être avancé plusieurs fois de suite : c'est ce que fait l'ordonnanceur quand il ne l'interrompt pas.

Rejouer le même entrelacement, un pas par poste dans l'ordre A, B, C, et constater qu'il ne fige plus rien. Demander le compte des entrelacements et vérifier qu'aucun ne laisse le compteur à 0.

Cinq cent trente-huit entrelacements, aucun interblocage. La vérification d'une convention d'ordre est mécanique : lister pour chaque fil la suite de ses acquisitions, et vérifier qu'elle est croissante pour l'ordre retenu.

Un service de réservation de créneaux d'atelier pose un verrou par atelier, créé à la volée quand l'atelier apparaît dans le planning. Une réservation peut porter sur deux ateliers à la fois. Comment définir un ordre d'acquisition sur des verrous qui n'existent pas encore à l'écriture du programme ?

L'ordre ne porte pas sur les verrous, qui n'ont ni nom ni rang durable, mais sur une clé stable de la ressource : l'identifiant de l'atelier dans le planning, par exemple. Tout verrou est associé à sa clé, et l'acquisition se fait par clés triées.

main.py
Sortie
>_ Prêt à exécuter…

Trois points méritent l'attention. La clé doit être stable : un identifiant, pas une position dans une liste ni une adresse en mémoire, qui changent d'une exécution à l'autre. Le dictionnaire des verrous est lui-même une donnée partagée, et sa création demande donc sa propre protection, ce que fait garde. Enfin, ce verrou de service se prend et se relâche avant toute acquisition de verrou d'atelier, ce qui le maintient hors de l'ordre commun.

Un technicien propose de résoudre l'interblocage de la cellule en ajoutant un délai d'expiration : si un fil n'obtient pas son second verrou en deux secondes, il libère le premier et recommence. Que vaut cette solution ?

Elle fonctionne, en ce sens que la cellule ne reste plus figée : le cycle d'attente est cassé par l'abandon, et le travail reprend. Elle a d'ailleurs un usage réel, quand l'ordre unique est impossible à imposer, par exemple entre deux logiciels développés séparément.

Ce n'est pourtant pas la bonne réponse par défaut, pour trois raisons.

Le délai transforme une panne franche en perte de performance diffuse : la cellule ne s'arrête plus, elle ralentit par à-coups de deux secondes, et le défaut disparaît des écrans de supervision sans avoir disparu du code.

Il ouvre la porte à une relance perpétuelle : deux fils qui abandonnent et recommencent en même temps peuvent se gêner indéfiniment, chacun reprenant le verrou que l'autre vient de libérer. C'est une famine, obtenue en voulant éviter un interblocage.

Il laisse enfin le programme dans un état à reprendre : le premier verrou a été relâché au milieu d'une opération, et il faut décider ce qu'il advient du travail déjà fait, question que l'ordre unique ne pose jamais.

L'ordre unique coûte une ligne et supprime le problème par construction. Le délai d'expiration est un filet de sécurité, utile quand rien d'autre n'est possible, jamais une dispense.

Vérification

Vérification rapideon peut se reprendre

1.Qu'est-ce qui caractérise un interblocage ?

2.Deux fils prennent deux verrous dans l'ordre inverse l'un de l'autre. Que peut-on dire du programme ?

3.Quelle règle supprime l'attente circulaire ?

4.L'ordre retenu pour les acquisitions doit-il suivre la logique du procédé industriel ?

5.Pourquoi un interblocage échappe-t-il aux tests ?

6.Qu'est-ce qui distingue la famine de l'interblocage ?

7.Un même fil acquiert deux fois un threading.Lock qu'il détient déjà. Que se passe-t-il ?

8.Que vaut un délai d'expiration sur l'acquisition, en remplacement de l'ordre unique ?

La méthode

  1. Lister les fils qui prennent plus d'un verrou : ce sont les seuls concernés, et ils sont en général peu nombreux.
  2. Écrire, pour chaque fil, la suite de ses acquisitions, dans l'ordre du programme, sur une seule ligne par fil.
  3. Tracer les attentes : chaque fil attend une ressource détenue par un autre, et un cycle dans ce tracé est un interblocage possible.
  4. Décider un ordre unique sur toutes les ressources, fondé sur une clé stable, et l'écrire à un endroit que le prochain rédacteur trouvera.
  5. Vérifier que chaque suite d'acquisitions est croissante pour cet ordre, fil par fil, sans jamais raisonner par paires de fils.
  6. Confier l'ordre au code plutôt qu'à la vigilance : une fonction d'acquisition qui trie les clés et empile les contextes, plutôt qu'une consigne écrite dans un document.
  7. Éprouver le résultat sur une figure : le compte des entrelacements ne doit plus en donner aucun qui laisse le travail inachevé.

Synthèse

  • Un interblocage est un état où chaque fil d'un groupe attend une ressource détenue par un autre : aucun n'a fini, aucun ne peut avancer, et la situation ne se dénoue jamais.
  • Sa cause est l'attente circulaire, qui apparaît dès que deux fils acquièrent deux verrous dans des ordres opposés.
  • Le code fautif est syntaxiquement parfait et logiquement juste : la faute porte sur l'ordre des acquisitions, ne se voit sur aucune ligne, et n'apparaît que sur une petite part des entrelacements, six sur quatre cent deux dans la cellule à trois postes.
  • La règle qui dissout le cycle est d'acquérir les verrous dans un ordre identique partout ; l'ordre choisi est indifférent, son unicité ne l'est pas, et l'ordre de libération n'a aucune importance.
  • Sur des verrous créés à la volée, l'ordre se définit sur une clé stable de la ressource, et se met en œuvre par un tri des clés suivi d'une pile de contextes.
  • La famine est un autre échec à progresser, mais d'une autre nature : le programme avance, et c'est un fil en particulier que les autres devancent sans cesse.

Les deux derniers chapitres ont ajouté des verrous, puis des règles pour que les verrous ne se retournent pas contre le programme. Le chapitre suivant change de modèle et pose la question autrement : et si les fils ne pouvaient être interrompus qu'aux endroits marqués par le rédacteur, la plupart de ces verrous auraient-ils encore lieu d'être ?