Współwłasność rzadko ma sens
Ta odpowiedź może być nieco styczna, ale muszę zapytać, ile przypadków ma sens z punktu widzenia użytkownika, aby podzielić się własnością ? Przynajmniej w domenach, w których pracowałem, praktycznie nie było żadnych, ponieważ w przeciwnym razie oznaczałoby to, że użytkownik nie musiałby po prostu usunąć czegoś raz z jednego miejsca, ale jawnie usunąć go od wszystkich odpowiednich właścicieli, zanim zasób faktycznie usunięty z systemu.
Często jest to pomysł inżynieryjny na niższym poziomie, aby zapobiec zniszczeniu zasobów, gdy coś jeszcze ma do nich dostęp, na przykład inny wątek. Często, gdy użytkownik prosi o zamknięcie / usunięcie / usunięcie czegoś z oprogramowania, należy je usunąć tak szybko, jak to możliwe (zawsze, gdy można je bezpiecznie usunąć), a na pewno nie powinno pozostać w pobliżu i powodować wycieku zasobów tak długo, jak to możliwe aplikacja jest uruchomiona.
Na przykład zasób gry w grze wideo może odwoływać się do materiału z biblioteki materiałów. Z pewnością nie chcemy, powiedzmy, zawieszającego się wskaźnika, jeśli materiał zostanie usunięty z biblioteki materiałów w jednym wątku, podczas gdy inny wątek nadal uzyskuje dostęp do materiału, do którego odnosi się zasób gry. Ale to nie znaczy, że zasoby gry nie mają sensu współdzielić własności materiałów, do których się odnoszą, z biblioteką materiałów. Nie chcemy zmusić użytkownika do jawnego usunięcia materiału z zasobów i biblioteki materiałów. Chcemy tylko upewnić się, że materiały nie zostaną usunięte z biblioteki materiałów, jedynego rozsądnego właściciela materiałów, dopóki inne wątki nie zakończą uzyskiwania dostępu do materiału.
Wycieki zasobów
Jednak współpracowałem z byłym zespołem, który objął GC za wszystkie komponenty oprogramowania. I chociaż to naprawdę pomogło upewnić się, że nigdy nie zniszczyliśmy zasobów, podczas gdy inne wątki nadal miały do nich dostęp, zamiast tego dostaliśmy naszą część wycieków zasobów .
Nie były to trywialne wycieki zasobów, które denerwują tylko programistów, jak kilobajt pamięci wyciekły po godzinnej sesji. Były to epickie wycieki, często gigabajty pamięci podczas aktywnej sesji, prowadzące do zgłoszeń błędów. Ponieważ teraz, gdy odwołanie do własności zasobu (a zatem współwłasności) między, powiedzmy, 8 różnymi częściami systemu, wystarczy, że jedna osoba nie usunie zasobu w odpowiedzi na użytkownika żądającego jego usunięcia wyciek i być może na czas nieokreślony.
Więc nigdy nie byłem wielkim fanem GC lub liczenia referencji stosowanych na szeroką skalę ze względu na to, jak łatwo stworzyli nieszczelne oprogramowanie. To, co poprzednio było zwisającą katastrofą wskaźnika, łatwą do wykrycia, zmienia się w bardzo trudny do wykrycia wyciek zasobów, który z łatwością może latać pod radarem testowania.
Słabe referencje mogą złagodzić ten problem, jeśli zapewnia je język / biblioteka, ale trudno mi było zdobyć zespół programistów o mieszanych umiejętnościach, aby móc konsekwentnie używać słabych referencji, gdy jest to właściwe. Trudność ta dotyczyła nie tylko wewnętrznego zespołu, ale każdego twórcy wtyczek do naszego oprogramowania. Oni również mogą łatwo spowodować wyciek zasobów z systemu poprzez przechowywanie trwałego odwołania do obiektu w sposób, który utrudniał odnalezienie wtyczki jako winowajcy, więc otrzymaliśmy również dużą część raportów o błędach wynikających z zasobów oprogramowania wyciekł po prostu dlatego, że wtyczka, której kod źródłowy był poza naszą kontrolą, nie opublikowała odniesień do tych drogich zasobów.
Rozwiązanie: odroczone, okresowe usuwanie
Więc moim późniejszym rozwiązaniem, które zastosowałem do moich osobistych projektów, które dało mi to, co najlepsze z obu światów, było wyeliminowanie koncepcji, która referencing=ownership
jednak odroczyła niszczenie zasobów.
W rezultacie teraz, ilekroć użytkownik robi coś, co powoduje, że zasób wymaga usunięcia, interfejs API jest wyrażany w kategoriach samego usunięcia zasobu:
ecs->remove(component);
... który modeluje logikę użytkownika w bardzo prosty sposób. Jednak zasób (komponent) nie może zostać usunięty od razu, jeśli w fazie przetwarzania znajdują się inne wątki systemowe, w których mogą uzyskiwać dostęp do tego samego komponentu jednocześnie.
Te wątki przetwarzania dają więc czas tu i tam, co pozwala wątkowi przypominającemu śmieciarz obudzić się i „ zatrzymać świat ” i zniszczyć wszystkie zasoby, których usunięcia zażądano, blokując wątki przed przetwarzaniem tych składników, aż do jego zakończenia. . Dostosowałem to tak, aby ilość pracy, którą trzeba tu wykonać, jest na ogół minimalna i nie zmniejsza zauważalnie liczby klatek na sekundę.
Teraz nie mogę powiedzieć, że jest to jakaś wypróbowana i przetestowana metoda, ale używam jej od kilku lat bez żadnych problemów i żadnych wycieków zasobów. Polecam zbadanie takich podejść, gdy jest to możliwe, aby Twoja architektura pasowała do tego rodzaju modelu współbieżności, ponieważ jest znacznie mniej obciążona niż GC lub liczenie ref i nie ryzykuje tego rodzaju wycieków zasobów latających pod radarem testowania.
Jedynym miejscem, w którym uważam, że przydatne jest liczenie odwołań lub GC, są trwałe struktury danych. W takim przypadku jest to terytorium struktury danych, dalekie od obaw użytkowników, i tam naprawdę niezmienne jest, aby każda niezmienna kopia potencjalnie współdzieliła własność tych samych niezmodyfikowanych danych.