La proprietà condivisa ha raramente senso
Questa risposta potrebbe essere leggermente non tangente, ma devo chiedermi, quanti casi ha senso dal punto di vista dell'utente per condividere la proprietà ? Almeno nei domini in cui ho lavorato, praticamente non ce n'erano perché altrimenti ciò implicherebbe che l'utente non deve semplicemente rimuovere qualcosa una volta da un posto, ma rimuoverlo esplicitamente da tutti i proprietari rilevanti prima che la risorsa sia effettivamente rimosso dal sistema.
Spesso è un'idea ingegneristica di livello inferiore impedire che le risorse vengano distrutte mentre qualcos'altro vi sta ancora accedendo, come un altro thread. Spesso quando un utente richiede di chiudere / rimuovere / eliminare qualcosa dal software, dovrebbe essere rimosso il più presto possibile (ogni volta che è sicuro da rimuovere), e certamente non dovrebbe indugiare in giro e causare una perdita di risorse per tutto il tempo l'applicazione è in esecuzione.
Ad esempio, una risorsa di gioco in un videogioco potrebbe fare riferimento a un materiale dalla libreria dei materiali. Certamente non vogliamo, diciamo, un crash puntatore penzolante se il materiale viene rimosso dalla libreria dei materiali in un thread mentre un altro thread sta ancora accedendo al materiale a cui fa riferimento l'asset di gioco. Ma ciò non significa che abbia senso che le risorse di gioco condividano la proprietà dei materiali a cui fanno riferimento con la libreria dei materiali. Non vogliamo forzare l'utente a rimuovere esplicitamente il materiale dalla libreria di risorse e materiali. Vogliamo solo assicurarci che i materiali non vengano rimossi dalla biblioteca del materiale, l'unico proprietario sensibile dei materiali, fino a quando altri fili non avranno finito di accedere al materiale.
Perdite di risorse
Eppure ho lavorato con un ex team che ha adottato GC per tutti i componenti del software. E mentre ciò ha davvero aiutato a garantire che non avessimo mai distrutto risorse mentre altri thread le stavano ancora accedendo, abbiamo invece ottenuto la nostra quota di perdite di risorse .
E queste non erano banali perdite di risorse di un tipo che sconvolge solo gli sviluppatori, come un kilobyte di memoria trapelato dopo un'ora di sessione. Si trattava di perdite epiche, spesso gigabyte di memoria su una sessione attiva, che portavano a segnalazioni di bug. Perché ora quando si fa riferimento alla proprietà di una risorsa (e quindi condivisa in proprietà) tra, diciamo, 8 diverse parti del sistema, allora ne basta uno solo per non riuscire a rimuovere la risorsa in risposta all'utente che ne richiede la rimozione essere trapelato e possibilmente indefinitamente.
Quindi non sono mai stato un grande fan del GC o del conteggio dei riferimenti applicati su larga scala a causa della facilità con cui sono stati creati software che perdono. Quello che sarebbe stato in precedenza un crash puntatore penzolante che è facile da rilevare si trasforma in una perdita di risorse molto difficile da rilevare che può facilmente volare sotto il radar dei test.
Riferimenti deboli possono mitigare questo problema se il linguaggio / la libreria li fornisce, ma ho trovato difficile ottenere un team di sviluppatori di skillset misti per poter utilizzare coerentemente riferimenti deboli quando appropriato. E questa difficoltà non era legata solo al team interno, ma a ogni singolo sviluppatore di plugin per il nostro software. Anche loro potrebbero facilmente causare la fuoriuscita di risorse dal sistema semplicemente memorizzando un riferimento persistente a un oggetto in modi che hanno reso difficile risalire al plugin come colpevole, quindi abbiamo anche ottenuto la parte del nostro leone di segnalazioni di errori derivanti dalle nostre risorse software essere trapelato semplicemente perché un plugin il cui codice sorgente era al di fuori del nostro controllo non è riuscito a rilasciare riferimenti a quelle risorse costose.
Soluzione: rimozione differita e periodica
Quindi la mia soluzione in seguito alla quale ho applicato i miei progetti personali che mi ha dato il meglio che ho trovato da entrambi i mondi è stata quella di eliminare il concetto che referencing=ownership
però ha ancora differito la distruzione delle risorse.
Di conseguenza, ora ogni volta che l'utente fa qualcosa che richiede la rimozione di una risorsa, l'API viene espressa in termini di rimozione della risorsa:
ecs->remove(component);
... che modella la logica dell'utente finale in modo molto semplice. Tuttavia, la risorsa (componente) non può essere rimossa immediatamente se ci sono altri thread di sistema nella loro fase di elaborazione in cui potrebbero accedere allo stesso componente contemporaneamente.
Quindi questi thread di elaborazione danno quindi tempo qua e là che consente a un thread che assomiglia a un garbage collector di svegliarsi e " fermare il mondo " e distruggere tutte le risorse che sono state richieste per essere rimosse bloccando i thread dall'elaborazione di quei componenti fino al termine . L'ho sintonizzato in modo che la quantità di lavoro che deve essere fatto qui sia generalmente minima e non si riduca notevolmente ai frame rate.
Ora non posso dire che questo sia un metodo provato e testato e ben documentato, ma è qualcosa che sto usando da alcuni anni ormai senza mal di testa e perdite di risorse. Raccomando di esplorare approcci come questo quando è possibile per la tua architettura adattarsi a questo tipo di modello di concorrenza in quanto è molto meno pesante di GC o di conteggio dei ref e non rischia questo tipo di perdite di risorse che volano sotto il radar dei test.
L'unico posto in cui ho trovato utili il conteggio dei ref o GC è per le strutture di dati persistenti. In quel caso è il territorio della struttura dei dati, ben distinto dalle preoccupazioni degli utenti, e lì ha davvero senso che ogni copia immutabile condivida potenzialmente la proprietà degli stessi dati non modificati.