Geteiltes Eigentum macht selten Sinn
Diese Antwort mag etwas tangential sein, aber ich muss fragen, in wie vielen Fällen ist es aus Anwendersicht sinnvoll, das Eigentum zu teilen ? Zumindest in den Domänen, in denen ich gearbeitet habe, gab es praktisch keine, da dies andernfalls bedeuten würde, dass der Benutzer nicht einfach einmal etwas von einem Ort entfernen muss, sondern es explizit von allen relevanten Eigentümern entfernen muss, bevor die Ressource tatsächlich vorhanden ist aus dem System entfernt.
Es ist oft eine untergeordnete technische Idee, um zu verhindern, dass Ressourcen zerstört werden, während noch etwas anderes darauf zugreift, wie ein anderer Thread. Wenn ein Benutzer etwas aus der Software schließen / entfernen / löschen möchte, sollte es häufig so schnell wie möglich entfernt werden (wann immer es sicher zu entfernen ist), und es sollte auf keinen Fall so lange verweilen und ein Ressourcenleck verursachen Die Anwendung wird ausgeführt.
Beispielsweise kann ein Spielelement in einem Videospiel auf ein Material aus der Materialbibliothek verweisen. Wir wollen sicher keinen Absturz eines baumelnden Zeigers, wenn das Material in einem Thread aus der Materialbibliothek entfernt wird, während ein anderer Thread noch auf das Material zugreift, auf das das Spiel-Asset verweist. Dies bedeutet jedoch nicht, dass es für Spiel-Assets sinnvoll ist, das Eigentum an Materialien, auf die sie verweisen, mit der Materialbibliothek zu teilen . Wir möchten den Benutzer nicht zwingen, das Material explizit aus dem Asset und der Materialbibliothek zu entfernen. Wir möchten nur sicherstellen, dass Materialien nicht aus der Materialbibliothek, dem einzigen vernünftigen Eigentümer von Materialien, entfernt werden, bis andere Fäden auf das Material zugegriffen haben.
Ressourcenlecks
Ich habe jedoch mit einem ehemaligen Team zusammengearbeitet, das GC für alle Komponenten der Software übernommen hat. Und während dies wirklich dazu beitrug, sicherzustellen, dass niemals Ressourcen zerstört wurden, während andere Threads noch auf sie zugegriffen haben, haben wir stattdessen unseren Anteil an Ressourcenlecks erhalten .
Und dies waren keine trivialen Ressourcenlecks, die nur Entwickler verärgern, wie ein Kilobyte Speicher, der nach einer einstündigen Sitzung verloren gegangen ist. Dies waren epische Lecks, oft Gigabyte Speicher während einer aktiven Sitzung, die zu Fehlerberichten führten. Denn wenn jetzt auf den Besitz einer Ressource verwiesen wird (und daher im Besitz von beispielsweise 8 verschiedenen Teilen des Systems geteilt wird), ist nur einer erforderlich, um die Ressource nicht zu entfernen, wenn der Benutzer anfordert, sie zu entfernen durchgesickert und möglicherweise auf unbestimmte Zeit.
Ich war also nie ein großer Fan von GC oder Referenzzählungen, die in großem Maßstab angewendet wurden, weil sie es einfach machten, undichte Software zu erstellen. Was früher ein baumelnder Zeigerabsturz gewesen wäre, der leicht zu erkennen ist, wird zu einem sehr schwer zu erkennenden Ressourcenleck, das leicht unter dem Radar der Tests fliegen kann.
Schwache Referenzen können dieses Problem mindern, wenn die Sprache / Bibliothek diese bereitstellt. Ich fand es jedoch schwierig, ein Team von Entwicklern mit gemischten Fähigkeiten dazu zu bringen, schwache Referenzen bei Bedarf konsistent zu verwenden. Und diese Schwierigkeit hing nicht nur mit dem internen Team zusammen, sondern mit jedem einzelnen Plugin-Entwickler für unsere Software. Auch sie können leicht dazu führen, dass das System Ressourcen verliert, indem sie lediglich einen dauerhaften Verweis auf ein Objekt auf eine Weise speichern, die es schwierig macht, auf das Plugin als Schuldigen zurückzugreifen. Daher haben wir auch den Löwenanteil der Fehlerberichte erhalten, die sich aus unseren Softwareressourcen ergeben Es wurde einfach durchgesickert, weil ein Plugin, dessen Quellcode außerhalb unserer Kontrolle lag, keine Verweise auf diese teuren Ressourcen freigeben konnte.
Lösung: Aufgeschobene, regelmäßige Entfernung
Meine Lösung, die ich später auf meine persönlichen Projekte anwendete, die mir das Beste aus beiden Welten gaben, bestand darin, das Konzept zu beseitigen, das referencing=ownership
die Zerstörung von Ressourcen verzögert hat.
Infolgedessen wird die API immer dann ausgedrückt, wenn der Benutzer etwas tut, das bewirkt, dass eine Ressource entfernt werden muss, indem nur die Ressource entfernt wird:
ecs->remove(component);
... die die Benutzerendlogik auf sehr einfache Weise modelliert. Die Ressource (Komponente) wird jedoch möglicherweise nicht sofort entfernt, wenn sich andere Systemthreads in ihrer Verarbeitungsphase befinden, in denen sie möglicherweise gleichzeitig auf dieselbe Komponente zugreifen.
Diese Verarbeitungsthreads geben dann hier und da Zeit, wodurch ein Thread, der einem Garbage Collector ähnelt, aufwacht und " die Welt stoppt " und alle Ressourcen zerstört, deren Entfernung angefordert wurde, während Threads von der Verarbeitung dieser Komponenten ausgeschlossen werden, bis sie fertig sind . Ich habe dies so eingestellt, dass der Arbeitsaufwand hier im Allgemeinen minimal ist und sich nicht merklich auf die Bildraten auswirkt.
Jetzt kann ich nicht sagen, dass dies eine bewährte und gut dokumentierte Methode ist, aber ich verwende sie seit einigen Jahren ohne Kopfschmerzen und ohne Ressourcenlecks. Ich empfehle, solche Ansätze zu untersuchen, wenn Ihre Architektur für diese Art von Parallelitätsmodell geeignet ist, da es viel weniger schwerfällig ist als GC oder Ref-Counting und diese Art von Ressourcenlecks nicht riskiert, wenn sie unter dem Radar des Testens fliegen.
Der einzige Ort, an dem ich Ref-Counting oder GC als nützlich empfand, ist für persistente Datenstrukturen. In diesem Fall handelt es sich um ein Datenstrukturgebiet, das weit entfernt von Bedenken des Benutzers ist, und dort ist es tatsächlich sinnvoll, dass jede unveränderliche Kopie möglicherweise das Eigentum an denselben unveränderten Daten teilt.