Vorrei suggerire che se si vuole implementare copy-on-write in modo efficiente (per le stringhe o qualsiasi altra cosa), si dovrebbe definire un tipo di involucro, che si comporterà come una stringa mutabile, e che si terrà sia annullabile riferimento a una stringa mutabile (nessun altro riferimento a quell'elemento sarà mai esistito) e un riferimento nullable a una stringa "immutabile" (riferimenti a cui non esisterà mai al di fuori di cose che non cercheranno di mutarlo). I wrapper verranno sempre creati con almeno uno di questi riferimenti non null; una volta che il riferimento di oggetto mutabile è mai stato impostato su un valore non nullo (durante o dopo la costruzione) si riferirà per sempre allo stesso obiettivo. Ogni volta che entrambi i riferimenti sono non nulli, il riferimento all'elemento immutabile indicherà una copia dell'articolo che è stata fatta qualche tempo dopo la mutazione completata più recente (durante una mutazione, il riferimento all'elemento immutabile può o non può contenere un riferimento a un valore di pre-mutazione).
Per leggere un oggetto, verificare se il riferimento "oggetto mutabile" non è nullo. Se è così, usalo. Altrimenti, controlla se il riferimento "oggetto immutabile" non è nullo. Se è così, usalo. Altrimenti, usa il riferimento "oggetto mutabile" (che ora sarà non nullo).
Per mutare un oggetto, verificare se il riferimento "oggetto mutabile" non è nullo. In caso contrario, copia il target del riferimento "oggetto immutabile" e confrontaExchange un riferimento al nuovo oggetto nel riferimento "oggetto mutabile". Quindi modifica la destinazione del riferimento "oggetto mutabile" e invalida il riferimento "oggetto immutabile".
Per clonare un oggetto, se si prevede che il clone verrà clonato nuovamente prima che venga mutato, recuperare il valore del riferimento "oggetto immutabile". Se è nullo, crea una copia del target "oggetto mutabile" e confrontaExchange un riferimento a quel nuovo oggetto nel riferimento all'elemento immutabile. Quindi crea un nuovo wrapper il cui riferimento "oggetto mutabile" è nullo e il cui riferimento "oggetto immutabile" è il valore recuperato (se non era nullo) o il nuovo elemento (se lo era).
Per clonare un oggetto, se si prevede che il clone venga mutato prima di essere clonato, recuperare il valore del riferimento "oggetto immutabile". Se null, recupera il riferimento "oggetto mutabile". Copia l'obiettivo di qualunque riferimento è stato recuperato e crea un nuovo wrapper il cui riferimento "oggetto mutabile" punta alla nuova copia e il cui riferimento "oggetto immutabile" è nullo.
I due metodi di clonazione saranno semanticamente identici, ma selezionando quello sbagliato per una determinata situazione si otterrà un'operazione di copia aggiuntiva. Se si sceglie coerentemente l'operazione di copia corretta, si otterrà il massimo vantaggio da un approccio "aggressivo" copy-on-write, ma con un sovraccarico molto meno threading. Ogni oggetto di conservazione dei dati (ad esempio una stringa) può essere non condiviso mutabile o condiviso immutabile e nessun oggetto passerà mai da uno stato all'altro.Di conseguenza, se lo si desidera, è possibile eliminare tutto il "sovraccarico di threading/sincronizzazione" (sostituendo le operazioni di CompareExchange con gli archivi diretti) a condizione che nessun oggetto wrapper sia utilizzato in più thread contemporaneamente. Due oggetti wrapper potrebbero contenere riferimenti allo stesso titolare di dati immutabili, ma potrebbero essere ignari dell'esistenza reciproca.
Si noti che alcune operazioni di copia potrebbero essere necessarie quando si utilizza questo approccio rispetto a quando si utilizza un approccio "aggressivo". Ad esempio, se un nuovo wrapper viene creato con una nuova stringa e tale wrapper viene mutato e copiato sei volte, il wrapper originale manterrà i riferimenti al titolare della stringa originale e uno immutabile contenente una copia dei dati. I sei wrapper copiati avrebbero solo mantenuto un riferimento alla stringa immutabile (due stringhe totali, sebbene se la stringa originale non fosse mai stata modificata dopo la copia, un'implementazione aggressiva potrebbe cavarsela con una). Se il wrapper originale fosse mutato, insieme a cinque delle sei copie, tutti i riferimenti tranne uno alla stringa immutabile sarebbero stati invalidati. A quel punto, se la sesta copia del wrapper venisse mutata, un'implementazione aggressiva di copia su scrittura potrebbe rendersi conto che conteneva l'unico riferimento alla sua stringa, e quindi decidere che una copia non era necessaria. L'implementazione che descrivo, tuttavia, creerebbe una nuova copia mutevole e abbandonerebbe quella immutabile. Nonostante il fatto che ci siano alcune operazioni di copia extra, tuttavia, la riduzione del sovraccarico di threading dovrebbe in molti casi più che compensare il costo. Se la maggior parte delle copie logiche prodotte non vengono mai mutate, questo approccio potrebbe essere più efficiente di fare sempre copie di stringhe.
Qual è la strategia di allocazione della memoria? Potresti voler affidarti a Pool Allocation per ottenere prestazioni migliori. –
Spero che questo sia solo per l'apprendimento. Ci sono così tanti trabocchetti per farlo funzionare correttamente in tutte le situazioni. –
solo per scopi di apprendimento ragazzi .. – fiveOthersWaiting