J'ai relu Herlihy et Wing plusieurs fois au cours des 15 dernières années. C'est une lecture très difficile. Et c'est dommage, car bien qu'il y ait quelques subtilités sur les bords, l'idée de base est en fait tout à fait raisonnable.
En bref: la linéarisation est comme la sérialisation, mais avec l'exigence supplémentaire que la sérialisation respecte des contraintes de commande supplémentaires entre les transactions. L'objectif est de vous permettre de raisonner rigoureusement sur une structure de données atomique individuelle plutôt que d'avoir à raisonner sur le système entier d'un seul coup.
La linéarisation est également facile à réaliser: il suffit d'associer un mutex à l'objet que vous souhaitez linéariser. Chaque transaction sur cet objet commence par verrouiller le mutex et se termine par déverrouiller le mutex.
Voici les définitions que j'utiliserai:
Un système est sérialisable s'il reçoit un ensemble de transactions sur un ensemble de données, tout résultat de l'exécution des transactions est le même que si les transactions ont été exécutées dans un ordre séquentiel et que les opérations de chaque transaction sont contenues dans leur transaction dans l'ordre spécifié par le code de la transaction.
La sérialisation interdit l'apparition de l'entrelacement des opérations entre les différentes transactions et requiert que l'ordre choisi des transactions satisfasse la causalité (si la transaction A écrit la valeur x et que la transaction B lit la valeur x que A a écrite, alors la transaction A doit précéder la transaction B dans l'ordre de série choisi.) Mais il ne dit rien sur les autres contraintes sur l'ordre des transactions (en particulier, il ne dit rien sur les processus et l'ordre dans lequel les processus perçoivent les événements.)
Il existe une autre idée connexe qui ajoute des contraintes sur l'ordre dans lequel les opérations exécutées sont traitées (mais ne parle pas des transactions uniquement des opérations de lecture / écriture individuelles):
Un système est séquentiellement cohérent si le résultat d'une exécution est le même que si les opérations de tous les processus ont été exécutées dans un certain ordre séquentiel, et les opérations de chaque processus individuel apparaissent dans cette séquence dans l'ordre spécifié par son programme. ( Lamport, "Comment faire un ordinateur multiprocesseur qui exécute correctement les programmes multiprocessus", IEEE T Comp 28: 9 (690-691), 1979 ).
La définition de la cohérence séquentielle implique implicitement que nous acceptons uniquement les ordres séquentiels où, pour chaque emplacement de mémoire (objet), l'ordre séquentiel des opérations induit obéit à la règle selon laquelle la valeur renvoyée par chaque opération de lecture à l'emplacement x
doit être la même que celle qui a été écrite par l'opération d'écriture immédiatement précédente à l'emplacement x
dans l'ordre séquentiel.
La linéarisation a les bonnes intentions de (a) combiner ensemble la notion de transactions (de sérialisation) avec la notion que les processus s'attendent à ce que les opérations qu'ils émettent se terminent dans l'ordre (par cohérence séquentielle) et (b) de restreindre les critères de correction pour parler de chacun objecter isolément, plutôt que de vous forcer à raisonner sur le système dans son ensemble. (Je voudrais pouvoir dire que l'implémentation de mon objet est correcte même dans un système où il y a d'autres objets qui ne sont pas linéarisables.) Je crois que Herlihy et Wing ont peut-être essayé de définir rigoureusement un moniteur .
La partie (a) est "facile": Une exigence séquentielle de consistance serait que les transactions sur l'objet émises par chaque processus apparaissent dans la séquence résultante dans l'ordre spécifié par le programme. Une exigence de type sérialisation serait que les transactions sur l'objet s'excluent toutes mutuellement (peuvent être sérialisées).
La complexité vient de l'objectif (b) (pouvoir parler de chaque objet indépendamment de tous les autres).
Dans un système avec plusieurs objets, il est possible que les opérations sur l'objet B imposent des contraintes sur l'ordre dans lequel nous pensons que les opérations ont été appelées sur l'objet A. Si nous examinons l'historique complet du système, nous serons contraints à certains ordres séquentiels, et devra rejeter les autres. Mais nous voulions un critère d'exactitude que nous pourrions utiliser de manière isolée (raisonnement sur ce qui arrive à l'objet A sans faire appel à l'historique du système global).
Par exemple: supposons que j'essaie de discuter de l'exactitude de l'objet A, qui est une file d'attente, supposons que l'objet B est un emplacement de mémoire, et supposons que j'ai les historiques d'exécution suivants: Thread 1: A.enqueue (x), A. dequeue () (renvoie y). Thread 2: A.enqueue (y), A.dequeue () (renvoie x). Y a-t-il un entrelacement d'événements qui permettrait à cette mise en œuvre de la file d'attente d'être correcte? Oui:
Thread 1 Thread 2
A.enqueue(x) ...
... A.enqueue(y)
... A.dequeue() (returns x)
A.dequeue(y) (returns y) ...
Mais maintenant que se passe-t-il si l'historique ( y compris l'objet B ) est: B commence par la valeur 0. Thread 1: A.enqueue (x), A.dequeue () (retourne y), B.write (1). Thread 2: B.read () (renvoie 1) A.enqueue (y), A.dequeue () (renvoie x).
Thread 1 Thread 2
A.enqueue(x) ...
A.dequeue() (returns y) ... (uh oh!)
B.write(1) ...
... B.read() (returns 1)
... A.enqueue(y)
... A.dequeue() (returns x)
Maintenant, nous aimerions que notre définition de "l'exactitude" dise que cette histoire indique que notre implémentation de A est boguée ou notre implémentation de B est boguée, car il n'y a pas de sérialisation qui "ait du sens" (soit le Thread 2 doit être lu une valeur de B qui n'a pas encore été écrite, ou le thread 1 doit retirer une valeur de A qui n'a pas encore été mise en file d'attente.) Ainsi, alors que notre sérialisation d'origine des transactions sur A semblait raisonnable, si notre implémentation permet une histoire comme la seconde, alors elle est clairement incorrecte.
Les contraintes que la linéarisation ajoute sont donc tout à fait raisonnables (et nécessaires même pour les structures de données simples comme les files d'attente FIFO.) Ce sont des choses comme: "votre implémentation devrait interdire dequeue () une valeur qui ne sera pas mise en file d'attente () avant un certain temps dans la futur." La linéarisation est assez facile (et naturelle) à réaliser: il suffit d'associer un mutex à votre objet, et chaque transaction commence par le verrouillage et se termine par le déverrouillage. Le raisonnement sur la linéarisation commence à devenir difficile lorsque vous essayez de mettre en œuvre votre atomicité avec des techniques non bloquantes ou sans verrouillage ou sans attente au lieu de simples mutex.
Si vous êtes intéressé par certains pointeurs de la littérature, j'ai trouvé ce qui suit (même si je pense que la discussion sur le "temps réel" est l'un des red-herrings qui rendent la linéarisation plus difficile qu'elle ne devrait l'être.) Https: // stackoverflow.com/questions/4179587/difference-between-linearizability-and-serializability