Ho riletto Herlihy e Wing molte volte negli ultimi 15 anni. È una lettura molto difficile. E questo è un peccato, perché mentre ci sono alcune sottigliezze ai bordi, l'idea di base è in realtà abbastanza ragionevole.
In breve: la linearizzabilità è come la serializzabilità, ma con il requisito aggiuntivo che la serializzazione rispetti ulteriori vincoli di ordinamento tra le transazioni. L'obiettivo è consentire di ragionare rigorosamente su una singola struttura di dati atomici anziché dover ragionare sull'intero sistema in una volta.
Anche la linearità è facile da ottenere: basta associare un mutex all'oggetto che si desidera linearizzare. Ogni transazione su quell'oggetto inizia bloccando il mutex e termina sbloccando il mutex.
Ecco le definizioni che userò:
Un sistema è serializzabile se viene dato un set di transazioni su un set di dati, qualsiasi risultato dell'esecuzione delle transazioni è lo stesso di se le transazioni fossero eseguite in un ordine sequenziale e le operazioni all'interno di ciascuna transazione sono contenute all'interno della loro transazione nell'ordine specificato dal codice della transazione.
La serializzazione non consente la comparsa di interfolli di operazioni tra diverse transazioni e richiede che l'ordinamento delle transazioni scelto soddisfi la causalità (se la transazione A scrive il valore x e la transazione B legge il valore x che A ha scritto, la transazione A deve precedere la transazione B in l'ordine seriale prescelto.) Ma non dice nulla su altri vincoli all'ordinamento delle transazioni (in particolare, non dice nulla sui processi e sull'ordine in cui i processi percepiscono gli eventi).
C'è un'altra idea correlata che aggiunge i vincoli sull'ordine in cui i processi hanno eseguito operazioni (ma non parla di transazioni solo singole operazioni di lettura / scrittura):
Un sistema è sequenzialmente coerente se il risultato di qualsiasi esecuzione è lo stesso di se le operazioni di tutti i processi sono state eseguite in un ordine sequenziale e le operazioni di ogni singolo processo compaiono in questa sequenza nell'ordine specificato dal suo programma. ( Lamport, "Come realizzare un computer multiprocessore che esegue correttamente i programmi multiprocesso", IEEE T Comp 28: 9 (690-691), 1979 ).
Nella definizione di coerenza sequenziale è implicito che accettiamo solo ordini sequenziali in cui per ogni posizione di memoria (oggetto) l'ordine sequenziale delle operazioni indotto obbedisce alla regola secondo cui il valore restituito da ciascuna operazione di lettura alla posizione x
deve essere lo stesso valore che è stato scritto da l'operazione di scrittura immediatamente precedente nella posizione x
nell'ordine sequenziale.
La linearità ha le buone intenzioni di (a) combinare la nozione di transazioni (dalla serializzazione) con la nozione che i processi prevedono che le operazioni che emettono vengano completate in ordine (da coerenza sequenziale) e (b) restringendo i criteri di correttezza per parlare di ciascuna oggetto in isolamento, piuttosto che costringervi a ragionare sul sistema nel suo insieme. (Vorrei poter dire che l'implementazione del mio oggetto è corretta anche in un sistema in cui ci sono altri oggetti che non sono linearizzabili.) Credo che Herlihy e Wing possano aver cercato di definire rigorosamente un monitor .
La parte (a) è "facile": un requisito sequenziale simile alla coerenza sarebbe che le transazioni sull'oggetto emesse da ciascun processo compaiano nella sequenza risultante nell'ordine specificato dal programma. Un requisito simile alla serializzazione sarebbe che le transazioni sull'oggetto si escludano a vicenda (possono essere serializzate).
La complessità deriva dall'obiettivo (b) (essere in grado di parlare di ciascun oggetto indipendentemente da tutti gli altri).
In un sistema con più oggetti è possibile che le operazioni sull'oggetto B impongano vincoli sull'ordine in cui crediamo che le operazioni siano state invocate sull'oggetto A. Se stiamo osservando l'intera cronologia del sistema, saremo vincolati a determinati ordini sequenziali, e dovrà rifiutare gli altri. Ma volevamo criteri di correttezza che potremmo usare da soli (ragionamento su cosa succede all'oggetto A senza fare appello alla storia del sistema globale).
Ad esempio: supponiamo che stia provando a discutere della correttezza dell'oggetto A, che è una coda, supponiamo che l'oggetto B sia un percorso di memoria e supponiamo che io abbia le seguenti cronologie di esecuzione: Thread 1: A.enqueue (x), A. dequeue () (restituisce y). Discussione 2: A.enqueue (y), A.dequeue () (restituisce x). Esiste un'interlacciamento di eventi che consentirebbe a questa implementazione della coda di essere corretta? Sì:
Thread 1 Thread 2
A.enqueue(x) ...
... A.enqueue(y)
... A.dequeue() (returns x)
A.dequeue(y) (returns y) ...
Ma ora cosa succede se la storia ( incluso l'oggetto B ) è: B inizia con il valore 0. Discussione 1: A.enqueue (x), A.dequeue () (restituisce y), B.write (1). Discussione 2: B.read () (restituisce 1) A.enqueue (y), A.dequeue () (restituisce 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)
Ora vorremmo che la nostra definizione di "correttezza" dicesse che questa storia indica che la nostra implementazione di A è buggy o la nostra implementazione di B è buggy, perché non esiste una serializzazione che "abbia senso" (il thread 2 deve leggere un valore da B che non è stato ancora scritto, oppure il thread 1 deve rimuovere un valore da A che non è stato ancora accodato.) Quindi, mentre la nostra serializzazione originale delle transazioni su A sembrava ragionevole, se la nostra implementazione consente una cronologia come la seconda, quindi è chiaramente errata.
Quindi i vincoli che la linearizzazione aggiunge sono abbastanza ragionevoli (e necessari anche per semplici strutture dati come le code FIFO). Sono cose del tipo: "l'implementazione dovrebbe impedire a dequeue () un valore che non sarà accodato () fino a qualche tempo nel futuro." La linearità è abbastanza facile (e naturale) da raggiungere: basta associare un mutex al tuo oggetto e ogni transazione inizia con il blocco e termina con lo sblocco. Il ragionamento sulla linearizzabilità inizia a diventare complicato quando si tenta di implementare la propria atomicità con tecniche non bloccanti o senza blocco o di attesa anziché semplici mutex.
Se sei interessato ad alcuni suggerimenti sulla letteratura, ho scoperto quanto segue (anche se penso che la discussione sul "tempo reale" sia una delle aringhe rosse che rendono la linearità più difficile di quanto debba essere.) Https: // stackoverflow.com/questions/4179587/difference-between-linearizability-and-serializability