Variablen Initialisierung mit lock_guard



  • @Leon0402 sagte in Variablen Initialisierung mit lock_guard:

    Oder kann *non_thread_safe_ptr irgendwo geändert werden (also das Objekt auf das non_thread_safe_ptr zeigt, nicht der non_thread_safe_ptr selbst)? Wenn ja, dann wäre das schlecht. Wenn nein, dann sollte das so gehen.

    Oh ja stimmt, geht natürlich so auchm klar. Nein das unterliegende Objekt wird nicht geändert, es ist const (Siehe Ergänzung letzter Post).

    Aus deiner Ergänzung geht nur hervor dass das Publisher/Subscriber Objekt nur einen const Zeiger hat. Aber nicht dass es keine andere Komponenten gibt die einen non-const Zeiger auf das selbe Objekt haben und es ändern könnten.

    Interessenshalber: Auch eine Zuweisung ist keine atomic Operation? Daher const auto copy = non_thread_safe_ptr geht nicht, richtig?

    Richtig.

    Mein Verständnis ist: Zwei verschiedene Shared ptr Objekte sind thread safe (auch wenn sie auf das selbe Objekt zeigen). Daher die shared ptr Magie selbst ist thread safe, wenn z.B. ein shared ptr zerstört wird und der counter dekrementiert wird.
    Alles was aber auf der selben shared ptr Instanz passiert (schreiben, dereferenzieren, kopieren, auf nullptr prüfen etc.) ist nicht thread safe, korrekt?

    Korrekt.

    Mit https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2 könnte man sich in dem Fall dann das Mutex hier sparen, korrekt?

    Korrekt. Wobei du davon ausgehen kannst dass so ein atomic<shared_ptr<T>> nicht "lock free" ist. D.h. der wird intern erst wieder etwas wie einen Lock-Pool verwenden. Wobei dann meistens Spin-Locks verwendet werden. D.h. es kann sein dass der Overhead etwas geringer wird, aber viel wird sich da nicht tun.

    Könnte man dann aktuell (nicht C++20) es auch so lösen:

    const auto copy = std::atomic_load(&non_thread_safe_ptr);
    

    Ja, allerdings nur wenn du an allen Stellen ausschliesslich mit den atomic_xxx Funktionen auf den shared_ptr zugreifst.



  • @hustbaer sagte in Variablen Initialisierung mit lock_guard:

    Aus deiner Ergänzung geht nur hervor dass das Publisher/Subscriber Objekt nur einen const Zeiger hat. Aber nicht dass es keine andere Komponenten gibt die einen non-const Zeiger auf das selbe Objekt haben und es ändern könnten.

    Ah ja. Die gibt es tatsächlich nicht. Das Framework nutzt leider überall shared ptr für keinen offensichtlichen Grund.

    Daher der subscriber callback liefert einen shared pointer als Parameter, der aktuell intern ebenfalls als shared ptr gespeichert wird. Evtl. wäre es auch eine Option sich hier gegen das Framework aufzulehnen und selbst nicht shared ptr zu übernutzen.

    Dann könnte man auch hier mit atomic arbeiten.

    @hustbaer sagte in Variablen Initialisierung mit lock_guard:

    Korrekt. Wobei du davon ausgehen kannst dass so ein atomic<shared_ptr<T>> nicht "lock free" ist. D.h. der wird intern erst wieder etwas wie einen Lock-Pool verwenden. Wobei dann meistens Spin-Locks verwendet werden. D.h. es kann sein dass der Overhead etwas geringer wird, aber viel wird sich da nicht tun.

    Mir geht es insgesamt auch mehr darum den Code schön zu machen. Wenn er dann auch noch etwas performanter ist, dann natürlich umso besser!

    @hustbaer sagte in Variablen Initialisierung mit lock_guard:

    Ja, allerdings nur wenn du an allen Stellen ausschliesslich mit den atomic_xxx Funktionen auf den shared_ptr zugreifst.

    Das ist machbar.



  • @Leon0402 sagte in Variablen Initialisierung mit lock_guard:

    @hustbaer sagte in Variablen Initialisierung mit lock_guard:
    Daher der subscriber callback liefert einen shared pointer als Parameter, der aktuell intern ebenfalls als shared ptr gespeichert wird. Evtl. wäre es auch eine Option sich hier gegen das Framework aufzulehnen und selbst nicht shared ptr zu übernutzen.

    Wieso? Ist in dem Fall doch super praktisch dass es ein shared_ptr ist.

    Dann könnte man auch hier mit atomic arbeiten.

    Verstehe ich nicht. Wie stellst du dir das vor? Willst du nen atomic<Foo> machen oder wie? Da ist shared_ptr<const Foo> fast sicher besser.

    atomic<Foo> macht mMn. nur Sinn wenn atomic<Foo>::is_always_lock_free ist. Was voraussetzt dass Foo nicht zu gross ist (üblicherweise max. 2x die Grösse eines Zeigers, auf manchen Plattformen aber auch nur 1x die Grösse eines Zeigers). Und dass Foo trivial kopierbar ist.



  • @hustbaer sagte in Variablen Initialisierung mit lock_guard:

    Verstehe ich nicht. Wie stellst du dir das vor? Willst du nen atomic<Foo> machen oder wie? Da ist shared_ptr<const Foo> fast sicher besser.
    atomic<Foo> macht mMn. nur Sinn wenn atomic<Foo>::is_always_lock_free ist. Was voraussetzt dass Foo nicht zu gross ist (üblicherweise max. 2x die Grösse eines Zeigers, auf manchen Plattformen aber auch nur 1x die Grösse eines Zeigers). Und dass Foo trivial kopierbar ist.

    Kannst du das näher erläutern? Also den ersten Teil, dass es trivial kopierbar ist mir bekannt. Ich konnte nicht ganz nachvollziehen, was "is_always_lock_free" bedeutet und waurm das wichtig wäre.



  • @Leon0402 Hab jetzt nicht alles gelesen. Kann es sein, dass du sowas suchst? https://www.grimm-jaud.de/index.php/blog/reader-writer-locks



  • @Leon0402 sagte in Variablen Initialisierung mit lock_guard:

    Ich konnte nicht ganz nachvollziehen, was "is_always_lock_free" bedeutet und waurm das wichtig wäre.

    Hardware-Atomics stehen nur bis zu einer bestimmten Grösse zur Verfügung. Für grössere Dinge werden Locks verwendet um die Zugriffe atomar zu machen. Und dann kann man mMn. auch gleich selbst Locks verwenden.



  • @Tyrdal sagte in Variablen Initialisierung mit lock_guard:

    @Leon0402 Hab jetzt nicht alles gelesen. Kann es sein, dass du sowas suchst? https://www.grimm-jaud.de/index.php/blog/reader-writer-locks

    Nicht wirklich. Es ist ja alles aktuell thread safe. Es ging nur darum wie man den Code lesbarer bekommt.

    @hustbaer sagte in Variablen Initialisierung mit lock_guard:

    Hardware-Atomics stehen nur bis zu einer bestimmten Grösse zur Verfügung. Für grössere Dinge werden Locks verwendet um die Zugriffe atomar zu machen. Und dann kann man mMn. auch gleich selbst Locks verwenden.

    Naja es ist halt lesbarer und spart Codezeilen. Ist das kein Argument für dich?

    const auto copy = [&]{
            std::lock_guard<std::mutex> lock(mutex);
            return non_thread_safe_ptr;
    }();
    

    vs.

    const auto copy = non_thread_safe;
    

    Was für mich dagegen spricht evtl. ist die Gefahr, dass man nicht mehr so richtig merkt, warum man eig. hier dann die Kopie macht etc.
    Aber bei beiden Lösungen sehe ich eig. eine ähnliche Gefahr, dass man einfach in größerem Code versehntlich doch eine Variable ohne Lock verwendet oder anderweitige Fehler macht.

    Oder was ist der Vorteil den du hier siehst, selbst Locks zu verwenden? -> Im allgemeinen Fall ist man vlt. etwas flexibler würde mir jetzt einfallen. In diesem speziellen Fall ist aber 1x schreiben, 1x lesen ja exakt, was ich möchte.



  • @Leon0402

    Evtl. wäre es auch eine Option sich hier gegen das Framework aufzulehnen und selbst nicht shared ptr zu übernutzen.
    Dann könnte man auch hier mit atomic arbeiten.

    Ich habe das so verstanden dass du einen atomic<Foo> statt eines atomic<shared_ptr<const Foo>> machen willst. Bei atomic<shared_ptr<const Foo>> hab ich weniger Bedenken.

    atomic<Foo> kommt mir aber immer etwas komisch vor. Alleine schon wegen der Einschränkungen. Es reicht ja z.B. nicht dass Foo trivial kopierbar ist. Es darf z.B. auch kein Padding enthalten. Wobei reine Loads und Stores vermutlich trotzdem immer funktionieren werden, aber bei allen read-modify-write Operationen wird es dann doof sobald Padding vorhanden ist. Ab einer bestimmten Grösse wird es dann auch effizienter einen atomic<shared_ptr<const Foo>> zu verwenden. Weil dabei halt nicht immer das ganze Objekt kopiert werden muss. Wobei man da vermutlich schon einige hudert wenn nicht sogar einige tausend Byte braucht um mit atomic<shared_ptr<const Foo>> schneller zu sein.



  • @Leon0402 sagte in Variablen Initialisierung mit lock_guard:

    vs.

    const auto copy = non_thread_safe;
    

    Was für mich dagegen spricht evtl. ist die Gefahr, dass man nicht mehr so richtig merkt, warum man eig. hier dann die Kopie macht etc.

    Meinst du

    const auto copy = thread_safe.load();
    

    ?
    Weil so wie du das geschrieben hast macht das für mich keinen Sinn. Weil 1) atomics nicht kopierbar sind und 2) der Name non_thread_safe dann nicht mehr passt. Ansonsten hab ich vermutlich nicht verstanden was du damit ausdrücken wolltest.



  • @Leon0402 sagte in Variablen Initialisierung mit lock_guard:

    Aber bei beiden Lösungen sehe ich eig. eine ähnliche Gefahr, dass man einfach in größerem Code versehntlich doch eine Variable ohne Lock verwendet oder anderweitige Fehler macht.

    Das lässt sich einfach lösen indem man Mutex + Datenstruktur in eine eigene Klasse verpackt die das kapselt.


Anmelden zum Antworten