Dies ist eine zweiteilige Frage, die sich mit der Atomizität von std::shared_ptr
:
1.
Soweit ich das beurteilen kann, std::shared_ptr
ist dies der einzige intelligente Zeiger <memory>
, der atomar ist. Ich frage mich, ob es eine nicht-atomare Version von std::shared_ptr
gibt (ich kann nichts darin sehen <memory>
, daher bin ich auch offen für Vorschläge außerhalb des Standards, wie die in Boost). Ich weiß, boost::shared_ptr
ist auch atomar (wenn BOOST_SP_DISABLE_THREADS
nicht definiert), aber vielleicht gibt es eine andere Alternative? Ich suche etwas, das die gleiche Semantik hat wie std::shared_ptr
, aber ohne die Atomizität.
2. Ich verstehe, warum std::shared_ptr
atomar ist; es ist irgendwie nett Es ist jedoch nicht für jede Situation schön, und C ++ hat in der Vergangenheit das Mantra "Bezahlen Sie nur für das, was Sie verwenden". Wenn ich nicht mehrere Threads verwende oder wenn ich mehrere Threads verwende, aber den Zeigerbesitz nicht auf mehrere Threads teile, ist ein atomarer intelligenter Zeiger übertrieben. Meine zweite Frage ist, warum std::shared_ptr
in C ++ 11 keine nichtatomare Version von bereitgestellt wurde . (vorausgesetzt, es gibt ein Warum ) (wenn die Antwort einfach "eine nichtatomare Version wurde einfach nie in Betracht gezogen" oder "niemand hat jemals nach einer nichtatomaren Version gefragt" lautet, ist das in Ordnung!).
Bei Frage Nr. 2 frage ich mich, ob jemand jemals eine nicht-atomare Version von shared_ptr
(entweder Boost oder dem Standardkomitee) vorgeschlagen hat (nicht um die atomare Version von zu ersetzen shared_ptr
, sondern um damit zu koexistieren), und sie wurde für eine abgeschossen bestimmter Grund.
quelle
shared_ptr
aufgrund seiner Atomizität eine erhebliche Verlangsamung darstellte und die DefinitionBOOST_DISABLE_THREADS
einen spürbaren Unterschied machte (ich weiß nicht, obstd::shared_ptr
die gleichen Kosten wie diese entstanden wärenboost::shared_ptr
).shared_ptr
Ich kann keine Antwort schreiben, jetzt ist es geschlossen, also kommentieren.) Wenn Ihr Programm nicht mehrere Threads verwendet, werden bei GCC keine atomaren Operationen für die Nachzählung verwendet. Unter (2) unter gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html finden Sie einen Patch für GCC, mit dem die nichtatomare Implementierung auch in Multithread-Apps fürshared_ptr
Objekte verwendet werden kann, die nicht gemeinsam genutzt werden Fäden. Ich habe jahrelang auf diesem Patch gesessen, aber ich denke darüber nach, ihn endlich für GCC 4.9Antworten:
Nicht vom Standard bereitgestellt. Möglicherweise wird eine von einer "Drittanbieter" -Bibliothek bereitgestellt. In der Tat schien es, dass vor C ++ 11 und vor Boost jeder seinen eigenen intelligenten Zeiger mit Referenzzählung schrieb (einschließlich meiner selbst).
Diese Frage wurde auf dem Rapperswil-Treffen im Jahr 2010 erörtert. Das Thema wurde durch einen National Body Comment # 20 der Schweiz vorgestellt. Auf beiden Seiten der Debatte gab es starke Argumente, einschließlich der von Ihnen in Ihrer Frage angegebenen. Am Ende der Diskussion war die Abstimmung jedoch überwiegend (aber nicht einstimmig) gegen das Hinzufügen einer nicht synchronisierten (nicht atomaren) Version von
shared_ptr
.Argumente gegen enthalten:
Code, der mit dem nicht synchronisierten shared_ptr geschrieben wurde, wird möglicherweise später in Thread-Code verwendet, was zu schwer zu debuggenden Problemen ohne Warnung führt.
Ein "universelles" shared_ptr zu haben, das die "Einbahnstraße" zum Verkehr bei der Referenzzählung darstellt, hat folgende Vorteile: Aus dem ursprünglichen Vorschlag :
Die Kosten der Atomik sind zwar nicht Null, aber nicht überwältigend. Die Kosten werden durch die Verwendung der Bewegungskonstruktion und der Bewegungszuweisung gemindert, bei denen keine atomaren Operationen verwendet werden müssen. Solche Operationen werden üblicherweise beim
vector<shared_ptr<T>>
Löschen und Einfügen verwendet.Nichts verbietet Menschen, ihren eigenen nicht-atomaren intelligenten Zeiger mit Referenzzählung zu schreiben, wenn sie das wirklich wollen.
Das letzte Wort der LWG in Rapperswil an diesem Tag war:
quelle
Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries.
das ist eine extrem seltsame Argumentation. Bibliotheken von Drittanbietern stellen sowieso ihre eigenen Typen zur Verfügung. Warum sollte es also wichtig sein, wenn sie diese in Form von std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType> usw. bereitstellen? Sie müssen Ihren Code immer an das anpassen, was die Bibliothek sowieso zurückgibtstd::shared_ptr<std::string>
irgendwohin gehen. Wenn die Bibliothek eines anderen auch diesen Typ verwendet, können Anrufer die gleichen Zeichenfolgen an uns beide weitergeben, ohne die Unannehmlichkeiten oder den Aufwand beim Konvertieren zwischen verschiedenen Darstellungen zu haben, und das ist ein kleiner Gewinn für alle.Howard hat die Frage bereits gut beantwortet, und Nicol machte einige gute Punkte zu den Vorteilen eines einzigen gemeinsamen Standardzeigertyps anstelle vieler inkompatibler Zeiger.
Obwohl ich der Entscheidung des Ausschusses voll und ganz zustimme, denke ich, dass die Verwendung eines nicht synchronisierten
shared_ptr
Typs in besonderen Fällen einen gewissen Vorteil hat. Deshalb habe ich das Thema einige Male untersucht.Wenn Ihr Programm bei GCC nicht mehrere Threads verwendet, verwendet shared_ptr keine atomaren Operationen für die Nachzählung. Dies erfolgt durch Aktualisieren der Referenzzähler über Wrapper-Funktionen, die erkennen, ob das Programm Multithreading ist (unter GNU / Linux erfolgt dies einfach durch Erkennen, ob das Programm mit verknüpft ist
libpthread.so
) und entsprechend an atomare oder nichtatomare Operationen gesendet werden.Ich habe vor vielen Jahren festgestellt, dass es möglich ist, die Basisklasse mit der Single-Threaded-Sperrrichtlinie auch in Multithread-Code zu verwenden, indem sie explizit verwendet wird , da GCCs
shared_ptr<T>
als__shared_ptr<T, _LockPolicy>
Basisklasse implementiert sind__shared_ptr<T, __gnu_cxx::_S_single>
. Da dies kein beabsichtigter Anwendungsfall war, funktionierte es leider vor GCC 4.9 nicht ganz optimal, und einige Vorgänge verwendeten immer noch die Wrapper-Funktionen und wurden daher an atomare Vorgänge gesendet, obwohl Sie die_S_single
Richtlinie ausdrücklich angefordert haben. Siehe Punkt (2) unter http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.htmlWeitere Details und einen Patch für GCC, damit die nicht-atomare Implementierung auch in Multithread-Apps verwendet werden kann. Ich habe jahrelang auf diesem Patch gesessen, ihn aber schließlich für GCC 4.9 festgelegt. Mit dieser Alias-Vorlage können Sie einen gemeinsamen Zeigertyp definieren, der nicht threadsicher, aber etwas schneller ist:Dieser Typ wäre nicht interoperabel
std::shared_ptr<T>
und nur dann sicher zu verwenden, wenn garantiert ist, dass dieshared_ptr_unsynchronized
Objekte ohne zusätzliche vom Benutzer bereitgestellte Synchronisierung niemals zwischen Threads geteilt werden.Dies ist natürlich völlig nicht portierbar, aber manchmal ist das in Ordnung. Mit den richtigen Präprozessor-Hacks würde Ihr Code mit anderen Implementierungen immer noch gut funktionieren, wenn
shared_ptr_unsynchronized<T>
es sich um einen Alias handelt.shared_ptr<T>
Mit GCC wäre er nur ein wenig schneller.Wenn Sie ein GCC vor 4.9 verwenden, können Sie dies verwenden, indem Sie die
_Sp_counted_base<_S_single>
expliziten Spezialisierungen zu Ihrem eigenen Code hinzufügen (und sicherstellen, dass niemand jemals__shared_ptr<T, _S_single>
ohne Einbeziehung der Spezialisierungen instanziiert , um ODR-Verstöße zu vermeiden). Das Hinzufügen solcher Spezialisierungen vonstd
Typen ist technisch undefiniert, würde dies aber tun Arbeiten Sie in der Praxis, denn in diesem Fall gibt es keinen Unterschied zwischen dem Hinzufügen der Spezialisierungen zu GCC oder dem Hinzufügen zu Ihrem eigenen Code.quelle
std::shared_ptr
,std::__shared_ptr
,__default_lock_policy
und so weiter . Diese Antwort bestätigte, was ich aus dem Code verstanden habe.Man könnte genauso gut fragen, warum es keinen aufdringlichen Zeiger gibt oder eine beliebige Anzahl anderer möglicher Variationen von gemeinsam genutzten Zeigern, die man haben könnte.
Das
shared_ptr
von Boost überlieferte Design bestand darin, eine Mindeststandard-Verkehrssprache für intelligente Zeiger zu schaffen. Das kann man im Allgemeinen einfach von der Wand ziehen und benutzen. Es ist etwas, das allgemein für eine Vielzahl von Anwendungen verwendet wird. Sie können es in eine Benutzeroberfläche einfügen, und die Chancen stehen gut, dass gute Leute bereit sind, es zu verwenden.Threading wird in Zukunft nur noch häufiger auftreten . In der Tat wird das Einfädeln im Laufe der Zeit im Allgemeinen eines der wichtigsten Mittel sein, um Leistung zu erzielen. Das Erfordernis, dass der grundlegende intelligente Zeiger das Nötigste tut, um das Threading zu unterstützen, erleichtert diese Realität.
Es wäre schrecklich gewesen, ein halbes Dutzend intelligenter Zeiger mit geringfügigen Abweichungen zwischen ihnen in den Standard oder noch schlimmer in einen richtlinienbasierten intelligenten Zeiger zu integrieren. Jeder würde den Zeiger auswählen, den er am liebsten mag, und alle anderen aufgeben. Niemand würde mit jemand anderem kommunizieren können. Es wäre wie in den aktuellen Situationen mit C ++ - Zeichenfolgen, in denen jeder seinen eigenen Typ hat. Nur weitaus schlimmer, da die Interaktion mit Zeichenfolgen viel einfacher ist als die Interaktion zwischen intelligenten Zeigerklassen.
Boost und damit auch das Komitee haben einen bestimmten intelligenten Zeiger ausgewählt, der verwendet werden soll. Es bot eine gute Ausgewogenheit der Merkmale und wurde in der Praxis häufig verwendet.
std::vector
hat einige Ineffizienzen im Vergleich zu nackten Arrays in einigen Eckfällen auch. Es hat einige Einschränkungen; Einige Anwendungen möchten wirklich eine feste Grenze für die Größe von a habenvector
, ohne einen Wurfzuweiser zu verwenden. Das Komitee wollte jedoch nichtvector
alles für alle sein. Es wurde als guter Standard für die meisten Anwendungen entwickelt. Diejenigen, für die es nicht funktionieren kann, können einfach eine Alternative schreiben, die ihren Bedürfnissen entspricht.Genau wie Sie es für einen intelligenten Zeiger können, wenn
shared_ptr
die Atomizität eine Belastung darstellt. Andererseits könnte man auch in Betracht ziehen, sie nicht so oft zu kopieren.quelle
Ich bereite einen Vortrag über shared_ptr bei der Arbeit vor. Ich habe einen modifizierten Boost shared_ptr verwendet, um ein separates Malloc zu vermeiden (wie es make_shared kann), und einen Vorlagenparameter für die oben erwähnte Sperrrichtlinie wie shared_ptr_unsynchronized. Ich benutze das Programm von
http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html
als Test nach dem Bereinigen der unnötigen shared_ptr-Kopien. Das Programm verwendet nur den Hauptthread und das Testargument wird angezeigt. Die Testumgebung ist ein Notebook mit Linuxmint 14. Hier ist die in Sekunden benötigte Zeit:
Nur die 'std'-Version verwendet -std = cxx11, und der -pthread wechselt wahrscheinlich lock_policy in der Klasse g ++ __shared_ptr.
Anhand dieser Zahlen sehe ich die Auswirkungen atomarer Anweisungen auf die Codeoptimierung. Der Testfall verwendet keine C ++ - Container,
vector<shared_ptr<some_small_POD>>
leidet jedoch wahrscheinlich, wenn das Objekt keinen Thread-Schutz benötigt. Boost leidet weniger wahrscheinlich, weil das zusätzliche Malloc das Inlining und die Codeoptimierung begrenzt.Ich habe noch keine Maschine mit genügend Kernen gefunden, um die Skalierbarkeit von atomaren Anweisungen einem Stresstest zu unterziehen, aber es ist wahrscheinlich besser, std :: shared_ptr nur bei Bedarf zu verwenden.
quelle
Boost bietet eine
shared_ptr
nicht atomare. Es heißtlocal_shared_ptr
und befindet sich in der Smart-Pointer-Bibliothek von Boost.quelle
shared_ptr
sowieso ein Zähler ist, obwohl es lokal ist? Oder meinst du damit ein anderes Problem? Die Dokumente sagen, dass der einzige Unterschied darin besteht, dass dies nicht atomar ist.local_shared_ptr
undshared_ptr
bis auf Atomic identisch ist. Ich bin wirklich daran interessiert herauszufinden, ob das, was Sie sagen, wahr ist, weil ich eslocal_shared_ptr
in Anwendungen verwende, die eine hohe Leistung erfordern.