Atomare Operationen - atomic<Foo>



  • Hallo zusammen,

    ab C++0x soll es ja ein Template atomic geben.
    Meine (naive?) Vorstellung davon ist, dass alle Datentypen die
    (architekturbedingt) atomar zugewiesen werden können, nur den Typ selbst
    beinhalten, alle anderen einen Mutex für den Zugriff verwenden.

    Deswegen verstehe ich folgendes nicht:

    #include<iostream>
    #include<atomic>
    #include<string>
    
    struct Foo{
            int i;
            double j;
            std::string s;
    };
    
    int main(int argc, char**) {
            std::cout << sizeof(int) << std::endl;
            std::cout << sizeof(double) << std::endl;
            std::cout << sizeof(std::string) << std::endl;
            std::cout << sizeof(Foo) << std::endl;
            std::cout << sizeof(std::atomic<int>) << std::endl;
            std::cout << sizeof(std::atomic<double>) << std::endl;
            std::cout << sizeof(std::atomic<std::string>) << std::endl;
            std::cout << sizeof(std::atomic<Foo>) << std::endl;
    }
    

    4
    8
    8
    24
    4
    8
    8
    24

    Warum stimmen die Größen von std::string und Foo mit deren Verwendung
    innerhalb von atomic überein?

    Sowas kann ja wohl unmöglich durch die Architektur atomar behandelbar sein.

    Ich glaube, ich habe ein völlig falsches Verständnis von atomic<Foo>...
    Oder wird der Kontext-Wechsel zwischen Threads irgendwie verhindert?
    (Kann ich mir eigentlich nicht vorstellen...)

    Gruß,
    XSpille

    EDIT: Code-Formatierung



  • XSpille schrieb:

    Oder wird der Kontext-Wechsel zwischen Threads irgendwie verhindert?
    (Kann ich mir eigentlich nicht vorstellen...)

    Warum kannst du dir das nicht vorstellen? Interrupts zu deaktivieren ist wohl das einfachste um ein Atomares verhalten herzustellen. Ob das im Atomic so gemacht wird kann ich nicht sagen.



  • XSpille schrieb:

    Ich glaube, ich habe ein völlig falsches Verständnis von atomic<Foo>...

    Woher stammt denn dieses Verständnis? Ich würde mal behaupten, atomic funktioniert nur mit int und Co, aber nicht mit selbst definierten Datentypen.



  • Fedaykin schrieb:

    Interrupts zu deaktivieren ist wohl das einfachste um ein Atomares verhalten herzustellen.

    Wie sollte das deaktivieren von Interrupts dabei helfen (insbesondere bei Multicore Prozessoren)?



  • Fedaykin schrieb:

    Warum kannst du dir das nicht vorstellen?

    Aus dem gleichen Grund wie manni66

    manni66 schrieb:

    XSpille schrieb:

    Ich glaube, ich habe ein völlig falsches Verständnis von atomic<Foo>...

    Woher stammt denn dieses Verständnis?

    Gute Frage...
    Hat sich so bei mir etabliert...
    Zum Beispiel durch Shade Of Mine:
    http://www.c-plusplus.net/forum/277608-10?highlight=atomic
    Ich hoffe ich hab da nichts falsch verstanden.

    Aber ich hab auch noch einen anderen interessanten Artikel
    (ich glaub von irgendeinem Microsoft-Typen) gelesen, aber den finde
    ich leider nicht wieder...
    Ich hoffe mal er hat nicht nur von der Microsoft-Implementierung geredet 🙄

    manni66 schrieb:

    Ich würde mal behaupten, atomic funktioniert nur mit int und Co, aber nicht mit selbst definierten Datentypen.

    Damit würde ich mich zufrieden geben, wenn mir noch jemand das "Co" definiert.
    Ein Member-Funktion-Pointer wäre (für mich) dann z. B. ein Grenzfall...

    DANKE EUCH,
    XSpille



  • Hallo,

    Vielleicht hilft das hier beim Verständnis.
    http://www.stdthread.co.uk/doc/headers/atomic/atomic.html



  • Braunstein schrieb:

    Hallo,

    Vielleicht hilft das hier beim Verständnis.
    http://www.stdthread.co.uk/doc/headers/atomic/atomic.html

    Danke Braunstein!!!

    Den Link hatte ich gerade auch gefunden und wollte ihn gerade posten und
    sehe du warst schneller 🤡



  • Aber die Klasse Foo verstößt(, wenn man std::string s rausnimmt) doch
    gegen keines der Kriterien, oder?

    Dann müsste doch das atomic-Template irgendwelche Synchronisierungsinformationen
    enthalten, oder?

    Zumindest nach der Erweiterung auf:

    struct Foo{
            int i0;
            int i1;
            int i2;
            int i3;
            int i4;
            int i5;
            int i6;
            int i7;
            int i8;
            int i9;
            double j0;
            double j1;
            double j2;
            double j3;
            double j4;
            double j5;
            double j6;
            double j7;
            double j8;
            double j9;
    };
    

    Die Größe beträgt (bei mir) jeweils 120 = 10 * int (4) + 10 * double (8)



  • Schau dir doch einfach die Implementierung in <atomic> an...
    Hier z.B. vom gcc: http://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen/a00754_source.html

    Und an den MAKROS _ATOMIC_LOAD_, _ATOMIC_STORE_ etc. aus <atomic_0.h> sieht man, daß nur lokale Variablen zum "Atomisieren" benötigt werden.



  • Das Stichwort hier heisst CAS (Compare and Set).

    Unter Windows gibt es zB InterlockedExchange dafür.

    Desto größer der Datentyp wird, desto unpraktischer wird CAS. Meine Implementierung kann zB nur ein maximum an 64bit. Für alles drüber müsste man locken (was bei mir aber nie vorkommt, daher hab ichs nie implementiert).

    Es ist nicht sinnvoll möglich non POD Typen in eine atomic Klasse zu packen. Im Prinzip müsste man mit boots::type_traits::is_pod darauf testen dass es wirklich ein POD ist. Denn wenn man komplexe Typen haben will, sind die Operationen ja sowieso nicht mehr atomar. Man kann zwar locken, aber da würde ich eine eigene Klasse dafür nehmen (da atomic dann ja die falsche Bezeichnung ist).

    Das Problem ist nämlich, dass es nicht wirklich möglich ist POD von nicht POD zu unterscheiden. boost::type_traits helfen, sind aber nicht fehlerfrei. Ich kenne einige Implementierungen die einfach auf int, char, long, etc. spezialisieren und alles andere verbieten - aber prinzipiell finde ich den Ansatz besser es nicht zu verbieten und mit 3 Rufzeichen in die Doku zu schreiben dass keine non-PODs erlaubt sind.

    Um zum Thema zu kommen: ich habe damals ja von einem atomic<Foo*> gesprochen und keinem atomic<Foo>. Mit Zeiger bin ich nämlich wieder auf den 64bit und fein raus 🙂

    PS:
    etwas code:
    der op+= sieht bei mir zB so aus:

    value_type operator-=(value_type amount) {
    		while(1) {
    			value_type cur = get();
    			value_type next = cur - amount;
    			if(compare_and_set(cur, next))
    				return cur;
    		}
    	}
    

    Wobei compare_and_set im Prinzip nichts anderes macht als

    bool compare_and_set_32(boost::uint32_t volatile* target, boost::uint32_t value, boost::uint32_t expected) {
    	return InterlockedCompareExchange(reinterpret_cast<LONG volatile*>(target), value, expected) == expected;
    }
    

    aufzurfuen und etwas alignment Magie, so dass man einen char eben mit einem 32Bit InterlockedExchange aufrufen kann.



  • Danke!

    @Shade Of Mine: Inzwischen gehe ich davon aus, dass du nicht über das C++0x-atomic redest, sondern einer Eigenimplementierung 🙂

    Ich bin nicht an einer InterlockedCompareExchange-Version interessiert, da
    ich Mac-User bin 😉

    #include<iostream>
    #include<atomic>
    
    class Foo{
    };
    
    std::atomic<int> a1;
    std::atomic<Foo*> a2;
    std::atomic<char> a3;
    std::atomic<bool> a4;
    std::atomic<long> a5;
    //std::atomic<double> a6;
    //std::atomic<float> a7;
    
    int main(int argc, char**) {
            a1 = 1;
            Foo* foo = new Foo();
            a2 = foo;
            a3 = 'a';
            a4 = true;
            a5 = 1l;
    //      a6 = 1.0d;
    //      a7 = 1.0f;
    }
    

    Inzwischen gehe ich davon aus, dass Zuweisungen nur kompilierbar sind, wenn sie
    auf dem System auch atomar sind. Also z.B. double und float sind auf meinem
    System nicht atomar.

    EDIT:

    Undefined symbols:
    "std::atomic<float>::store(float, std::memory_order) volatile", referenced from:
    std::atomic<float>::operator=(float) in cc5J0Qrp.o
    "std::atomic<double>::store(double, std::memory_order) volatile", referenced from:
    std::atomic<double>::operator=(double) in cc5J0Qrp.o
    ld: symbol(s) not found
    collect2: ld returned 1 exit status



  • @Shade Of Mine: myAtomic.compare_exchange_strong scheint die C++0x-Umsetzung
    (und damit die zukünftig standard-konforme Version) von
    InterlockedCompareExchange zu sein.
    http://www.stdthread.co.uk/doc/headers/atomic/atomic/compare_exchange_strong.html

    Wofür könnte die weak-Version davon (sinnvoll) verwenden?
    http://www.stdthread.co.uk/doc/headers/atomic/atomic/compare_exchange_weak.html



  • XSpille schrieb:

    @Shade Of Mine: Inzwischen gehe ich davon aus, dass du nicht über das C++0x-atomic redest, sondern einer Eigenimplementierung 🙂

    Ja, aber die Technik ist dieselbe.

    Ich bin nicht an einer InterlockedCompareExchange-Version interessiert, da
    ich Mac-User bin 😉

    http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/atomic.3.html
    selbes Prinzip

    Inzwischen gehe ich davon aus, dass Zuweisungen nur kompilierbar sind, wenn sie
    auf dem System auch atomar sind. Also z.B. double und float sind auf meinem
    System nicht atomar.

    Wie gesagt: bis 64bit kann man ohne Probleme gehen. Einige Implementierungen, scheinbar auch die die du verwendest, haben aber eine fixe spezialisierung für die builtins und scheinbar eben nicht für die fließkomma Typen.

    Auf die schnelle fällt mir zwar nicht ein warum das so sein könnte, aber vielleicht ist die Implementierung auf 32bit limitiert oder derartiges...

    Zu weak/strong siehe: http://en.wikipedia.org/wiki/Load-link/store-conditional


Anmelden zum Antworten