Threadwechsel unterdrücken



  • Hallo!

    In einem Thread habe ich mehrere aufeinanderfolgende Code-Zeilen, die umbedingt hintereinander abgearbeitet werden müssen (es darf kein anderer Thread dazwischen drankommen). Welche Möglichkeiten gibt es ???

    mfg archangel



  • Hallo

    Du kannst andere Threads mit Hilfe von Mutexen auschliessen, hier findest du meher darüber http://www.pronix.de/ckurs/22thread3.html .

    gruss sam



  • Hi,

    es geht also darum, dass ein Codebereich nur von einem Thread ausgeführt wird. Threadwechsel müssen deshalb nicht unterdrückt werden, der andere Thread muss dann lediglich solange warten, bis der Bereich wieder freigegeben wird. Die geht mit Mutex. Zuerst ein Beispiel:

    //global
    pthread_mutex_t code_access;
    // irgendwo vor der Erzeugung des Threads/der Threads
    pthread_mutex_init( &code_access, NULL ); // Variable initialisieren 
    
    // in einer Funktion:
    pthread_mutex_lock( &code_access ); // Mutex sperren
    ... // kritischer Programmcode
    pthread_mutex_unlock( &code_access ); // Mutex freigeben
    

    Der Grund ist folgender, wenn ein Mutex unter Linux 'gelockt' ist, und ein 2. Thread will locken, muss der 2. solange warten, bis der Mutex wieder freigegeben wird. Dies geschieht in dem Beispiel erst nach dem Programmcode. Dadurch können beide Threads nicht denselben Programmcode durchlaufen.
    Natürlich ist das nicht auf EIN Porgrammblock beschränkt, in Wirklichkeit kann man den Mutex auch an mehreren Stellen verwendet. Das ist deshalb sinnvoll, weil Zugriffe auf Variablen das Problem sind, und auf solche Variablen kann man von mehreren Stellen aus zugreifen.
    Wenn man ein 'unlock' vergisst, kann dies zu heftigen Problemen führen - also am besten gleich nach dem 'lock' ein 'unlock' schreiben und das Programm dann zwischen den Zeilen einfügen.

    Das war nur ein kleiner Crashkurs, falls es noch Fragen gibt - nur keine Hemmungen images/smiles/icon_smile.gif

    cu



  • Thx.

    Nur ...
    ... das mit dem Mutex ist schon klar. Und das Problem ist genau der Zugriff auf eine Variable.
    Diese globale Variable wird im Main-Thread und in anderen Threads als Index für ein Array (das ebenfalls global ist) benutzt.
    Und in einem bestimmten Thread wird diese Variable kurzfristig verändert, wovon der Main-Thread und die anderen jedoch nichts "mitbekommen" sollen.
    Also müßte ich die Code-Zeilen in diesem Thread zwischen ein mutex_lock und ein mutex_unlock einfügen und jedesmal wenn im Main-Thread oder einem anderen Thread diese Variable verwendet wird, ebenfalls ein mutex_lock davor und ein mutex_unlock danach einfügen.
    Das Problem dabei ist, daß die Variable 100te mal im Main-Thread verwendet wird und ich den Aufwand, jedesmal ein mutex_lock und ein mutex_unlock einzufügen, vermeiden wollte.

    Archangel



  • Ja genau, dazu sind die Dinger da. Allerdings brauchst Du für einen statischen MUTEX nur
    static pthread_mutex_t name = PTHREAD_MUTEX_INITIALIZER; global einfügen. Die Variante mit pthread_mutex_init ist für dynamische Zuweisungen oder Shared Memory.

    [ 20.08.2001: Beitrag editiert von: Joris ]



  • @Archangel
    So einfach geht das nicht. Du mußt in des Tat an jeder Stelle lock/unlock einfügen - viel Spaß ...

    Das ist aber ein gutes Beispiel, warum globale Variablen wirklich schädlich sind. Das sie global sind, ist kein Problem, dass aber jeder darauf zugreifen kann, ist das echte Problem - denn jeder muss vorher lock/unlock aufrufen. Deshalb sollte man globale Variablen immer kapseln, in C++ kann man auch ein Objekt daraus machen. Und nur Zugriffsfunktionen dürfen diese Variablen ändern:

    int get_arrayvalue( size_t index ) {
      lock();
      int result = array[index];
      unlock();
      return result;
    }
    
    void set_arrayvalue( size_t index, int value ) {
      lock();
      array[index] = value;
      unlock();
    }
    

    Natürlich benötigt man, je nach Art der globalen Variable, mehr und klomplexere Funktionen. Vielleicht kannst du dein Programm noch umstellen, sonst musst du wirklich jeden Zugriff in lock/unlock einschließen.

    Auch wenn dir die Antwort/Lösung nicht gefällt, ich bin schuldlos images/smiles/icon_wink.gif

    cu

    [ 20.08.2001: Beitrag editiert von: Fux ]



  • Darf ich noch kurz darauf hinweisen, daß die Fuxsche Methode natürlich richtig ist, aber nicht ganz unproblematisch wenn innerhalb von lock und unlock eine Exception geworfen wird. Dann wird nämlich der lock nicht mehr freigegeben und das Programm bleibt irgendwann stehen (Deadlock).

    Ich habe diesbezüglich unter Windows schon nette Erfahrungen gemacht. Wir haben dazu eine abstrakte Klasse erzeugt, die threadsicheren Zugriff an Klassen vererben kann. Ich übertrage das mal freihand auf Linux, wenn's irgendwo hakt merkt Ihr das schon:

    class Threadsafe
    {
    private:
       pthread_mutex_t m_code_access;
    protected:
       Threadsafe()
       {
           pthread_mutex_init(&m_code_access, NULL);
       };
       ~Threadsafe() {};
    public:
       void Lock()
       { 
          pthread_mutex_lock(&m_code_access);
       }
       void Unlock()
       {
          pthread_mutex_unlock(&m_code_access);
       }
    };
    
    class SingleLock
    {
    private:
       Threadsafe* m_pThreadsafe;
    public:
       SingleLock(Threadsafe* threadsafe, bool lock = false)
       {
          m_pThreadsafe = threadsafe;
          if (lock)
             Lock();
       }
       ~SingleLock()
       {
          Unlock();
       }
       void Lock()
       {
          if (m_pThreadsafe)
             m_pThreadsafe->Lock();
       }
       void Unlock()
       {  
          if (m_pThreadsafe)
             m_pThreadsafe->Unlock();
       }
    };
    

    Wollen wir nun folgende Klasse threadsicher machen:

    class Container
    {
    private:
       int m_Value;
    public:
       void setVal(int value) {m_Value = value;};
       int getVal() {return m_Value;};
    };
    

    Das wird zu

    class Container : public Threadsafe
    {
    private:
       int m_Value;
    public:
       void setVal(int value) 
       {
          SingleLock slock(this);   // entweder explizit locken
          slock->Lock();
          m_Value = value;
          slock->Unlock();
       };
       int getVal() 
       {                               // oder implizit
          SingleLock slock(this, true); // ab hier ist gesperrt
          return m_Value;
          // hier wird automatisch aufgeräumt
       };
    };
    

    Dann kann das Teil Exceptions werfen wie es will, oder auch ein Rücksprung aus einer Funktion mit mehreren returns bringt keine Probleme. Sobald das Objekt gelöscht wird, ruft es automatisch Unlock auf, das wiederum Zugriff auf das mutex-Handle hat.

    Aber man kann auch als Aufrufer explizit Lock/Unlock von Container aufrufen... das kann je nach Situation Sinn machen, daß die Funktionen intern locken, daß aber auch der Aufrufer das machen kann. Wer das nicht will, muß Lock/Unlock in Threadsafe private machen und SingleLock als friend definieren.

    Das sollte so weitgehend richtig auch unter Linux funktionieren, ist fast analog zu Win, nur andere Handle/Funktionsnamen.

    Falls es dafür schon eine fertige Klasse gibt... nun gut, dann war's halt zur Übung. Das Ding läßt sich auch noch etwas verfeinern um zweimaliges Unlock zu verhindern etc. Geht mehr um die Idee. images/smiles/icon_wink.gif

    [ 20.08.2001: Beitrag editiert von: Marc++us ]



  • Ach so, noch ein Wort: dies setzt vom Klassendesign voraus, daß man immer nur auf Klassenebene die gesamte Klasse locken kann, nicht jede Variable einzeln. Aber dies hat sich bei uns für die Praxis als absolut ausreichend und auch vom Design her korrekt erwiesen.



  • Bei Paaren wie lock/unlock, sehe ich natürlich immer das Exceptionproblem - aber ich ging hier erstmal von C aus, also hab ich nichts dazu gesagt - im C++-Forum wäre es was anderes. Abzuleiten halte ich für keine gute Idee (ich habs selbst mal mit einem template gemacht). Das wollt ich auch nicht diskutieren. Vor ein paar Tagen hab ich einen Artikel von Stroustrup gelesen, und der hat meine Template-Variante noch erweitert und verbessert. In http://www.research.att.com/~bs/papers.html unter "Wrapping C++ Member Functions Call" ist eine PDF-Datei.
    Mit einem etwas vereinfachten Syntax würde man sowas schreiben:

    Wrapper<Container> container( new Container ); // Wrapper wrappt die Threadsicherheit
    
    container->setValue( 5 ); // vor dem Aufruf von Container::setValue wird automatisch gelockt, und danach entlockt (vor dem Semikolon)
    

    In Wirklichkeit muss man dem Wrapper noch sagen, wie gelockt wird.

    cu

    [ 20.08.2001: Beitrag editiert von: Fux ]



  • Das mit den Deadlocks ist natürlich richtig, allerdings ist das mit Euren Exceptions interessant. Was versteht Ihr denn da so genau unter Linux? Wenn hier ein einzelner User-Level Thread abschmiert, sind eh alle anderen weg.



  • Es handelt sich (wie zu lesen) um C++-Exceptions, nicht um Signale. Exception müssen nicht zwangsläufig das Ende eines Threads bedeuten, und selbst wenn, dann auch nicht unbedingt das Ende des Programms. Auf tiefer Linuxebene (Kernel) geht Exceptionhandling (leider) nicht, aber auf höherer Ebene darf man Exception nutzen.
    Achja, Deadlock wird nicht prinzipiell vermieden, es wird nur vermieden, dass man 'aus Versehen' ein 'unlock' vergisst, 2 Locks im selben Thread (und damit ein Deadlock (1) ) können durch schlechte Programmierung immer noch auftreten, jedoch machen sowohl Marcus' Version als auch meine dies programmtechnisch schwerer.

    (1) Ich gehe dabei von einem normalen Mutex aus (fast mutex). Durch einen Errormutex oder rekursiven Mutex, werden Deadlocks durch 2 folgende Locks verhindert, aber diese Threadarten betrachte ich eher als Notnagel (rekursiv) oder nutze sie zum Debuggen (Errormutex).

    cu

    [ 21.08.2001: Beitrag editiert von: Fux ]



  • Da weiß ich ja gleich noch, was ich mir mal genauer ansehen muß.


Anmelden zum Antworten