To jest trochę dwuczęściowe pytanie, dotyczące atomowości std::shared_ptr
:
1.
O ile wiem, std::shared_ptr
jest to jedyny inteligentny wskaźnik w <memory>
tym atomowym. Zastanawiam się, czy jest std::shared_ptr
dostępna nieatomowa wersja programu (w środku nic nie widzę <memory>
, więc jestem również otwarty na sugestie spoza standardu, jak te w Boost). Wiem, że boost::shared_ptr
jest również atomowy (jeśli BOOST_SP_DISABLE_THREADS
nie jest zdefiniowany), ale może jest inna alternatywa? Szukam czegoś, co ma taką samą semantykę jak std::shared_ptr
, ale bez atomowości.
2. Rozumiem, dlaczego std::shared_ptr
jest atomowy; to całkiem miłe. Jednak nie jest to dobre rozwiązanie w każdej sytuacji, a C ++ zawsze wyznawał mantrę „płać tylko za to, czego używasz”. Jeśli nie używam wielu wątków lub jeśli używam wielu wątków, ale nie udostępniam własności wskaźnika między wątkami, atomowy inteligentny wskaźnik jest przesadą. Moje drugie pytanie brzmi: dlaczego nie atomowa wersja nie została std::shared_ptr
udostępniona w C ++ 11 ? (zakładając, że istnieje przyczyna ) (jeśli odpowiedź brzmi po prostu „wersja nieatomowa nigdy nie była brana pod uwagę” lub „nikt nigdy nie prosił o wersję nieatomową” to w porządku!).
Odnośnie pytania nr 2, zastanawiam się, czy ktoś kiedykolwiek zaproponował nieatomową wersję shared_ptr
(do Boost lub komisji normalizacyjnej) (nie po to, aby zastąpić atomową wersję shared_ptr
, ale aby z nią współistnieć) i została zestrzelona specyficzny powód.
źródło
shared_ptr
był znacznym spowolnieniem ze względu na jego atomowość, a zdefiniowanieBOOST_DISABLE_THREADS
spowodowało zauważalną różnicę (nie wiem, czystd::shared_ptr
miałoby ten sam koszt, coboost::shared_ptr
wcześniej).shared_ptr
, nie używa atomic ops do refcount. Zobacz (2) na gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html, aby zapoznać się z poprawką do GCC, która umożliwia użycie nieatomowej implementacji nawet w aplikacjach wielowątkowych dlashared_ptr
obiektów, które nie są współużytkowane między wątki. Siedzę nad tą łatką od lat, ale rozważam ostateczne wydanie jej na GCC 4.9Odpowiedzi:
Niedostępne w standardzie. Może być udostępniony przez bibliotekę „innej firmy”. Rzeczywiście, przed C ++ 11 i przed Boost, wydawało się, że każdy pisał własne referencje liczone inteligentnym wskaźnikiem (łącznie ze mną).
Kwestia ta była omawiana na spotkaniu w Rapperswil w 2010 roku. Temat został wprowadzony w komentarzu nr 20 organu krajowego Szwajcarii. Po obu stronach debaty pojawiły się silne argumenty, w tym te, które przedstawiłeś w swoim pytaniu. Jednak pod koniec dyskusji głosowanie było przeważające (ale nie jednomyślne) przeciwko dodaniu niezsynchronizowanej (nie atomowej) wersji
shared_ptr
.Zawiera argumenty przeciwko:
Kod napisany za pomocą niezsynchronizowanego shared_ptr może w końcu zostać użyty w kodzie podzielonym na wątki, powodując trudne do debugowania problemy bez ostrzeżenia.
Posiadanie jednego „uniwersalnego” shared_ptr, który jest „jedyną drogą” do ruchu w zliczaniu referencji, ma zalety: Z pierwotnej oferty pakietowej :
Koszt atomów, choć nie jest zerowy, nie jest przytłaczający. Koszt jest zmniejszony przez zastosowanie konstrukcji ruchu i przypisania ruchu, które nie wymagają użycia operacji atomowych. Takie operacje są powszechnie używane w
vector<shared_ptr<T>>
usuwaniu i wstawianiu.Nic nie stoi na przeszkodzie, aby ludzie pisali własny, nieatomowy wskaźnik liczony jako odniesienie, jeśli naprawdę tego chcą.
Ostatnie słowo LWG w Rapperswilu tego dnia brzmiało:
źródło
Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries.
to bardzo dziwne rozumowanie. Biblioteki stron trzecich i tak dostarczą własne typy, więc dlaczego miałoby to mieć znaczenie, gdyby udostępniły je w postaci std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType> itp.? zawsze będziesz musiał dostosować swój kod do tego, co i tak zwraca bibliotekastd::shared_ptr<std::string>
gdzieś zająć . Jeśli biblioteka innej osoby również przyjmuje ten typ, wywołujący mogą przekazywać te same ciągi do nas obu bez niedogodności lub narzutów związanych z konwersją między różnymi reprezentacjami, a to mała korzyść dla każdego.Howard już dobrze odpowiedział na to pytanie, a Nicol przedstawił kilka dobrych uwag na temat korzyści wynikających z posiadania jednego standardowego typu współdzielonego wskaźnika, zamiast wielu niekompatybilnych.
Chociaż całkowicie zgadzam się z decyzją komisji, uważam, że stosowanie w szczególnych przypadkach niezsynchronizowanego
shared_ptr
typu podobnego do tego przynosi pewne korzyści , więc zbadałem ten temat kilka razy.Z GCC, gdy twój program nie używa wielu wątków shared_ptr nie używa atomic ops do refcount. Odbywa się to poprzez aktualizację liczników odwołań za pomocą funkcji opakowujących, które wykrywają, czy program jest wielowątkowy (w systemie GNU / Linux odbywa się to po prostu przez wykrycie, czy program jest powiązany z
libpthread.so
) i odpowiednio wysyłają do operacji atomowych lub nieatomowych.Wiele lat temu zdałem sobie sprawę, że ponieważ GCC
shared_ptr<T>
jest implementowane w postaci__shared_ptr<T, _LockPolicy>
klasy bazowej , możliwe jest użycie klasy bazowej z jednowątkową polityką blokowania nawet w kodzie wielowątkowym, poprzez jawne użycie__shared_ptr<T, __gnu_cxx::_S_single>
. Niestety, ponieważ nie był to zamierzony przypadek użycia, nie działał optymalnie przed GCC 4.9, a niektóre operacje nadal korzystały z funkcji opakowujących i wysyłane do operacji atomowych, mimo że wyraźnie zażądałeś tej_S_single
polityki. Patrz punkt (2) na http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.htmlaby uzyskać więcej szczegółów i łatkę do GCC, aby umożliwić stosowanie nieatomowej implementacji nawet w aplikacjach wielowątkowych. Siedziałem nad tą poprawką przez lata, ale w końcu zdecydowałem się na to dla GCC 4.9, które pozwala ci użyć szablonu aliasu, takiego jak ten, do zdefiniowania typu współdzielonego wskaźnika, który nie jest bezpieczny dla wątków, ale jest nieco szybszy:template<typename T> using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;
Ten typ nie byłby interoperacyjny
std::shared_ptr<T>
i byłby bezpieczny w użyciu tylko wtedy, gdy jest zagwarantowane, żeshared_ptr_unsynchronized
obiekty nigdy nie będą współużytkowane między wątkami bez dodatkowej synchronizacji dostarczonej przez użytkownika.Jest to oczywiście całkowicie nieprzenośne, ale czasami jest to w porządku. Z właściwymi hackami preprocesora, twój kod nadal działałby dobrze z innymi implementacjami, gdyby
shared_ptr_unsynchronized<T>
był aliasem dlashared_ptr<T>
, byłby trochę szybszy z GCC.Jeśli używasz GCC w wersji wcześniejszej niż 4.9, możesz to wykorzystać, dodając
_Sp_counted_base<_S_single>
jawne specjalizacje do własnego kodu (i upewniając się, że nikt nigdy nie tworzy instancji__shared_ptr<T, _S_single>
bez uwzględnienia specjalizacji, aby uniknąć naruszeń ODR). Dodanie takich specjalizacjistd
typów jest technicznie niezdefiniowane, ale byłoby w praktyce, bo w tym przypadku nie ma różnicy między mną dodaniem specjalizacji do GCC, a Tobą dodaniem ich do własnego kodu.źródło
std::shared_ptr
,std::__shared_ptr
,__default_lock_policy
i takie. Ta odpowiedź potwierdziła to, co zrozumiałem z kodu.Równie łatwo można zapytać, dlaczego nie ma natrętnego wskaźnika lub dowolnej liczby innych możliwych odmian wspólnych wskaźników, jakie można mieć.
Projekt
shared_ptr
, przekazany przez Boost, miał na celu stworzenie minimalnego standardu lingua-franca inteligentnych wskaźników. To, ogólnie rzecz biorąc, możesz po prostu zdjąć to ze ściany i użyć. Jest to coś, co byłoby powszechnie używane w wielu różnych zastosowaniach. Możesz umieścić go w interfejsie i są duże szanse, że ludzie będą chcieli go używać.W przyszłości wątkowanie stanie się bardziej powszechne. Rzeczywiście, w miarę upływu czasu wątki będą na ogół jednym z głównych sposobów osiągnięcia wydajności. Wymóg, aby podstawowy inteligentny wskaźnik wykonywał absolutne minimum potrzebne do obsługi wątków, ułatwia tę rzeczywistość.
Wrzucenie do standardu pół tuzina inteligentnych wskaźników z niewielkimi różnicami między nimi lub, co gorsza, inteligentnego wskaźnika opartego na polityce, byłoby straszne. Każdy wybrałby wskazówkę, którą najbardziej lubił, i wyrzekł się wszystkich innych. Nikt nie byłby w stanie komunikować się z nikim innym. Byłoby to tak, jak obecne sytuacje z napisami C ++, gdzie każdy ma swój własny typ. Tylko o wiele gorsze, ponieważ współdziałanie z łańcuchami jest o wiele łatwiejsze niż współdziałanie między klasami inteligentnych wskaźników.
Boost, a co za tym idzie, komitet wybrał konkretny inteligentny wskaźnik do użycia. Zapewniała dobrą równowagę funkcji i była szeroko i powszechnie stosowana w praktyce.
std::vector
ma pewne nieefektywności w porównaniu z gołymi tablicami w niektórych przypadkach narożnych. Ma pewne ograniczenia; niektóre zastosowania naprawdę chcą mieć sztywny limit rozmiaru avector
, bez używania alokatora rzucającego. Jednak komitet nie zaprojektował tak,vector
aby być wszystkim dla wszystkich. Został zaprojektowany tak, aby był dobrym domyślnym dla większości aplikacji. Ci, dla których to nie zadziała, mogą po prostu napisać alternatywę, która odpowiada ich potrzebom.Tak jak dla inteligentnego wskaźnika, jeśli
shared_ptr
atomowość jest ciężarem. Z drugiej strony można by też rozważyć nie kopiowanie ich tak często.źródło
Przygotowuję referat na temat shared_ptr w pracy. Używałem zmodyfikowanego boost shared_ptr z unikaniem oddzielnego malloc (jak to, co może zrobić make_shared) i parametru szablonu dla zasad blokowania, takich jak shared_ptr_unsynchronized, o którym mowa powyżej. Używam programu z
http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html
jako test, po wyczyszczeniu niepotrzebnych kopii shared_ptr. Program używa tylko głównego wątku i wyświetlany jest argument testowy. Środowisko testowe to komputer przenośny z systemem LinuxMint 14. Oto czas w sekundach:
Tylko wersja „std” używa -std = cxx11, a opcja -pthread prawdopodobnie przełącza lock_policy w klasie g ++ __shared_ptr.
Na podstawie tych liczb widzę wpływ atomowych instrukcji na optymalizację kodu. Przypadek testowy nie używa żadnych kontenerów C ++, ale
vector<shared_ptr<some_small_POD>>
prawdopodobnie ucierpi, jeśli obiekt nie potrzebuje ochrony wątku. Boost cierpi mniej prawdopodobnie, ponieważ dodatkowy malloc ogranicza ilość wstawiania i optymalizacji kodu.Nie znalazłem jeszcze maszyny z wystarczającą liczbą rdzeni, aby przetestować skalowalność instrukcji atomowych, ale użycie std :: shared_ptr tylko wtedy, gdy jest to konieczne, jest prawdopodobnie lepsze.
źródło
Boost zapewnia to
shared_ptr
, co nie jest atomowe. Nazywa sięlocal_shared_ptr
i można go znaleźć w bibliotece inteligentnych wskaźników doładowania.źródło
shared_ptr
z licznikiem, mimo że jest lokalny? A może masz na myśli inny problem? Doktorzy mówią, że jedyną różnicą jest to, że to nie jest atomowe.local_shared_ptr
ishared_ptr
są identyczne z wyjątkiem atomowego. Naprawdę jestem zainteresowany, aby dowiedzieć się, czy to, co mówisz, jest prawdą, ponieważ używamlocal_shared_ptr
w aplikacjach wymagających wysokiej wydajności.