Multithreading und Synchronisierung



  • Optimizer schrieb:

    rapso schrieb:

    Optimizer schrieb:

    Das schlimmste was passieren könnte ist, dass da drin kompletter Unsinn steht. Das ist aber auch vom Typ der Variablen und vom Prozessor abhängig. Bei nem UINT im speziellen ist es wahrscheinlich auf den meisten Prozessoren so, dass eine Zuweisung atomar ist, also hast du voll den alten oder voll den neuen Wert. Aber die Garantie gibt's nicht.

    es ist nicht garantiert dass die zuweisung atomar ist, der uint32_t wert kann genau so gut auf zwei speichersegmente aufgeteilt sein. atomar darf man bei uint8_t annehmen.

    Das sollte auf Grund des alignments durch den Compiler nicht passieren. Aber klar, ich würde auf jeden Fall den Zugriff synchronisieren. So viel Gründlichkeit muss einfach sein.

    aber garantieren kann man es nicht. einfach mal ein compilerflag aendern, oder ein memorymapped file, oder noch einfacher, umcasten von pointern. gibt bei manchen systemen nen absturz, aber beim x86 laeuft das normal weiter.



  • ProgChild schrieb:

    Optimizer schrieb:

    rapso schrieb:

    es ist nicht garantiert dass die zuweisung atomar ist, der uint32_t wert kann genau so gut auf zwei speichersegmente aufgeteilt sein. atomar darf man bei uint8_t annehmen.

    Das sollte auf Grund des alignments durch den Compiler nicht passieren. Aber klar, ich würde auf jeden Fall den Zugriff synchronisieren. So viel Gründlichkeit muss einfach sein.

    Ich weis zwar nicht wie, aber ich hab mit 10 Threads gleichzeitig eine Variable Hochgezählt. Also jeder Thread hat die Variable um eins erhöht. Der Wert wurde vorher auf 0 gesetzt. Als ich die Variable mal ohne Lock ausgelesen hab, wärend die anderen Threads noch liefen, hatte ich plötzlich einen negativen Wert. (System: i686-Prozessor mt einem Kern, GCC-Compiler)

    hmm.. bei nur einem core ist das ohne hardware/software bugs eigentlich nicht moeglich. es gibt ja zu jeder zeit nur ein abbild des speichers im cache. vielleicht war die variable uninitialisiert? 😃



  • rapso schrieb:

    hmm.. bei nur einem core ist das ohne hardware/software bugs eigentlich nicht moeglich. es gibt ja zu jeder zeit nur ein abbild des speichers im cache. vielleicht war die variable uninitialisiert? 😃

    Neee... die war statisch und global initialisiert. Ich hab nur den Quelltext, wo der Fehler auftrat nicht mehr... Der Fehler war weg, als ich den Mutex gesetzt hatte. Ich weis auch nicht, wie ich das hinbekommen hab 😃 Vielleicht lag der Fehler auch wo anders.

    Was ich sagen will ist, dass einfach Alles passieren kann, wenn man keine Mutexe setzt.

    Ein anderes Beispiel:

    #include <stdlib.h>
    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    
    int counter = 0;
    
    void* t_fun (void* p) {
        int n;
    
        n = counter;
        n++;
    
        usleep(1000 * (rand() % 1000));
    
        printf("Counter: %d - n: %d\n", counter, n);
    
        counter = n;
    
        printf("Counter: %d\n", counter);
    }
    
    int main (int argc, char* argv[]) {
        int i, j;
        pthread_t threads[10];
    
        srand(time(NULL));
    
        for (i=0; i<10; i++) {
            pthread_create(&threads[i], NULL, &t_fun, NULL);
        }
    
        for (i=0; i<10; i++) {
            void* rv;
            pthread_join(threads[i], &rv);
        }
    
        // Hier sollte eigentlich 10 ausgegeben werden
        printf("Counter Main: %d :P\n", counter);
    
        return 0;
    }
    

    Was schief geht, ist klaar, aber der Compiler könnte genau so eine Situation generieren. Durch optimierung oder sonst was...

    Zum Beispiel könnte er die Variable zwecks Optimierung erst in ein Register schreiben, dann inkrementieren und dann zurückschreiben. Wenn wärdendessen ein Threadwechsel stattfindet, hat man ein Problem.



  • Ein Problem kann man genauso haben wenn der Compiler einfach nur ein "inc" draus macht und garnix in Registern hält, nämlich sobald ein 2. Core dazukommt, oder je nach System genauso ohne 2. Core (manche CPUs unterbrechen "mehrschrittige" Befehle für Interrupts, und wenn's der Scheduler Interrupt war...).

    Die Sache ist einfach die: man muss wissen was man tun kann und was nicht. Wenn man mit Mutexen arbeitet ist das recht einfach, daher sollte man sich daran halten. Wenn man anfängt ohne Mutexen und mit atomic instructions/memory barriers etc. zu arbeiten wird's verdammt haarig, und blöderweise sind die Fehler die dann entstehen verdammt schwer zu finden (z.B. weil sie die Tendenz haben kaum reproduzierbar zu sein und z.T. auch nur sehr sehr selten auftreten).



  • Tyrdal schrieb:

    also schrieb:

    bei nem standard PC den jeder zuhause hat, kannst du davon ausgehen, dass kein Unsinn darin stehen wird. Ich glaub es gibt keine möglichkeit gleichzeitig in eine Speicherzelle zu schreiben und daraus zu lesen. und da ein UINT in ein register passt, sollte immer was sinnvolles drinstehen, außer du hast eine Architektur, bei der ein UINT nicht in ein Register passt.

    Das ist ja wohl kompletter Unsinn. Wozu gibt denn Speicher wenn keiner reinschreiben darf? Und natürlich kann Unsinn im Speicher stehen. Einfachstes Beospiel: int i; -> in i steht Unsinn, weil uninitialisiert.

    Lerne: Lesen, Denken, Verstehen



  • Und du lerne zu wissen und nicht nur zu glauben. Es gibt nämlich Möglichkeiten, daß aus Sicht eines Threads gleichzeitig mehrmals in eine Speicherstelle geschrieben wird. Daher wird manchmal Unsinn drin stehen (wenn du nich lockst oder wirklich atomare Instruktionen verwendest). Außerdem gibts auch noch andere Hardware als Standard-PCs.


Anmelden zum Antworten