Laden...

Viele Threads eine Datenklasse und ein Deadlock

Erstellt von thinkJD vor 13 Jahren Letzter Beitrag vor 13 Jahren 2.490 Views
T
thinkJD Themenstarter:in
9 Beiträge seit 2010
vor 13 Jahren
Viele Threads eine Datenklasse und ein Deadlock

Hallo Leute,
ich habe ein Problem, mit dem ich nicht weiter komme.

Ich habe mehrere Threads, die alle auf denselben Datenbestand arbeiten.
Dazu habe ich mir eine Datenklasse gebaut, die nur aus den Variablen und Getter sowie setter besteht. Also ähnlich einem Struct nur als Klasse, da ich einen Referenztyp brauche.

In einer Schicht darüber habe ich eine Liste, die eine Anzahl x dieser Datenklassen enthält. Diese Klasse enthält neben der Liste Methoden um der Liste Objekte hinzuzufügen, zu löschen usw.

Jetzt kommt es immer mal wieder zu einem Deadlock und ich habe keinen Schimmer warum.


    public class ZaehlerAufBand
    {
        private clsWasserzaehlerliste m_Liste;
        private log4net.ILog Logger;
        public string PfadVolumenteildatei { get; private set; }


        public ZaehlerAufBand(string PfadVolumenteilDatei)
        {
            log4net.Config.XmlConfigurator.Configure();
            Logger = log4net.LogManager.GetLogger("ZaehlerAufBand");
            m_Liste = new clsWasserzaehlerliste();

            this.PfadVolumenteildatei = PfadVolumenteildatei;
            if (System.IO.File.Exists(PfadVolumenteilDatei)) Deserialisieren(PfadVolumenteilDatei);
        }

        public void addWasserZaehler(WasserZaehler Zaehler)
        {
            m_Liste.Add(Zaehler);
            Logger.Info(string.Format("Volumenteil {0} auf Band hinzugefügt", Zaehler.VT_Fernum));
        }

        public bool removeWasserZaehler(string VT_Fernum)
        {
            if (m_Liste.Count > 0)
            {
                foreach (WasserZaehler tmp in m_Liste)
                {
                    if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                    {
                        m_Liste.Remove(tmp);
                        Logger.Info(string.Format("Volumenteil {0} von Band entfernt", tmp.VT_Fernum));
                        return true;
                    }
                }
            }
            Logger.Error(string.Format("Volumenteil mit der Fertigungsnummer: {0} nicht gefunden", VT_Fernum));
            return false;
        }

        public bool ZaehlerVorhanden(string VT_Fernum)
        {
            if (m_Liste == null) return false;

            foreach (WasserZaehler tmp in m_Liste)
            {
                if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                {
                    Logger.Debug("Zähler ist in Liste vorhanden");
                    return true;
                }
            }
            Logger.Debug("Zähler ist nicht in Liste vorhanden");
            return false;
        }

        public WasserZaehler getZaehler(string VT_Fernum)
        {
            foreach (WasserZaehler tmp in m_Liste)
            {
                if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                {
                    return tmp;
                }
            }

            WasserZaehler temp = new WasserZaehler();
            temp.Dummy = true;
            return temp;
        }

        public int getVolumenteileImSpeicher
        {
            get { return m_Liste.Count; }
        }

        public void Speicher_Loeschen()
        {
            m_Liste.Clear();
            Serealisieren(PfadVolumenteildatei);
        }

Die Klasse war früher ein Singleton, das habe ich aufgrund der beschriebenen Problematik geändert. Eine Besserung gab es trotzdem nicht.
Ich hoffe ihr könnt mir weiterhelfen.
Gruß JD

916 Beiträge seit 2008
vor 13 Jahren

Hallo thinkJD,

wie synchronisierst du denn deine Listen Zugriffe? Daher zeig mal bisschen Code von der Klasse die deine ZaehlerAufBand Klasse verwendet. Ich befürchte du synchronisiert dort nämlich nix, und daher kommt es zu einem "Deadlock" bzw. 2 Threads greifen "unkoordiniert" auf die Liste deiner Klasse zu.

Außerdem hast du erwähnt das die Klasse Singleton war jetzt aber nicht mehr ist, kannst du das bitte genauer erklären. Daher hat jeder Thread jetzt eine eigene Liste (ZaehlerAufBand) oder nicht?

Again what learned...

T
thinkJD Themenstarter:in
9 Beiträge seit 2010
vor 13 Jahren

Stimmt ich synchronisiere nichts 😃
Ich greife einfach zu. Früher hatte ich die Klasse (aus diesem Grund) als Singleton ausgelegt. Dann dachte ich der Deadlock kommt daher und habe die Klasse umgeschrieben. Das Problem ist aber geblieben.

Heute Morgen habe ich meine Versionsverwaltung angeworfen und den alten Zustand wieder hergestellt. Die Klasse aktuell (und mit gleichem Problem) so aus:


   sealed class ZaehlerAufBand : Datenbank.DatenObjekt
    {
        private clsWasserzaehlerliste m_Liste;
        private log4net.ILog Logger;
        public string PfadVolumenteildatei { get; private set; }
        private bool init = false;
        private string VT_Fernum_temp;
        private int ID_Benutzer_temp;
        private clsBackup ProgResultBackup;
        private System.Timers.Timer m_t_Serealisieren;


        static readonly ZaehlerAufBand m_instance = new ZaehlerAufBand();
        public static ZaehlerAufBand Instance
        {
            get { return m_instance; }
        }
        private ZaehlerAufBand() : base() { }

        public bool Initialisieren(INIFile Einstellungen)
        {
            if (this.init) return true;

            try
            {
                this.init = true;
                log4net.Config.XmlConfigurator.Configure();
                Logger = log4net.LogManager.GetLogger("ZaehlerAufBand");
                m_Liste = new clsWasserzaehlerliste();
                this.PfadVolumenteildatei = Einstellungen.getValue("Pfade", "DatenDateiVolumenteile");
                if (System.IO.File.Exists(this.PfadVolumenteildatei)) this.Deserialisieren(Einstellungen.getValue("Pfade", "DatenDateiVolumenteile"));
                return true;
            }

            catch (Exception ex)
            {
                Logger.Error("Initialisieren des ZaehlerAufBand Singletons ist fehlgeschlagen", ex);
                this.init = false;
                return false;
            }
        }
        private void Checkinit()
        {
            if (!(init)) throw new Exception("Das Objekt wurde noch nicht initialisiert");
        }


        public void addWasserZaehler(WasserZaehler Zaehler)
        {
            Checkinit();

            m_Liste.Add(Zaehler);
            Logger.Info(string.Format("Volumenteil {0} auf Band Hinzugefügt", Zaehler.VT_Fernum));
        }

        public bool removeWasserZaehler(string VT_Fernum)
        {
            Checkinit();
            //Logger.Info("Achung! Volumenteile werden nicht gelöscht! Siehe [ZaehlerAufBand]");
            //return true;

            if (m_Liste.Count > 0)
            {
                foreach (WasserZaehler tmp in m_Liste)
                {
                    if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                    {
                        m_Liste.Remove(tmp);
                        Logger.Info(string.Format("Volumenteil {0} von Band entfernt", tmp.VT_Fernum));
                        return true;
                    }
                }
            }
            Logger.Error(string.Format("Volumenteil mit der Fertigungsnummer: {0} nicht gefunden", VT_Fernum));
            return false;
        }

        public bool ZaehlerVorhanden(string VT_Fernum)
        {
            if (m_Liste == null) return false;

            foreach (WasserZaehler tmp in m_Liste)
            {
                if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                {
                    Logger.Debug("Zähler ist in Liste vorhanden");
                    return true;
                }
            }
            Logger.Debug("Zähler ist nicht in Liste vorhanden");
            return false;
        }

        public WasserZaehler getZaehler(string VT_Fernum)
        {
            //Gibt einen Zähler aus der Liste zurück
            foreach (WasserZaehler tmp in m_Liste)
            {
                if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                {
                    return tmp;
                }
            }

            WasserZaehler temp = new WasserZaehler();
            temp.Dummy = true;
            return temp;
        }

     
        public int getVolumenteileImSpeicher
        {
            get { return m_Liste.Count; }
        }
        public void Speicher_Loeschen()
        {
            m_Liste.Clear();
            Serealisieren(PfadVolumenteildatei);
        }
     
        private bool Serealisieren(string Pfad)
        {
            try
            {
                Checkinit();

                if (File.Exists(Pfad)) { File.Delete(Pfad); }

                using (FileStream fstrThis = new FileStream(Pfad, FileMode.CreateNew))
                {
                    XmlSerializer xserThis = new XmlSerializer(typeof(clsWasserzaehlerliste));
                    xserThis.Serialize(fstrThis, m_Liste);
                    fstrThis.Close();
                }
                Logger.Debug("Speichern der Volumenteildaten erfolgreich");
            }
            catch (Exception ex)
            {
                Logger.Error("Wasserzaehlerliste konnte nicht serialisiert werden", ex);
                return false;
            }

            return true;
        }
        private bool Deserialisieren(string Pfad)
        {
            try
            {
                m_Liste = null;

                using (FileStream fstrThis = new FileStream(Pfad, FileMode.Open))
                {
                    XmlSerializer xserThis = new XmlSerializer(typeof(clsWasserzaehlerliste));
                    m_Liste = (clsWasserzaehlerliste)xserThis.Deserialize(fstrThis);
                    fstrThis.Close();
                }

                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("Datendatei konnte nicht geladen werden");
                Logger.Error(ex.Message);
                m_Liste = new clsWasserzaehlerliste();
                return false;
            }

        }
    }

Zugriff erfolgt einfach über die Variable Instance-> Komponenten.ZaehlerAufBand.Instance.ZaehlerVorhanden(VT_Fernum);

Siehst du da ein Problem? Wie könnte ich den Zugriff koordinieren? Es gibt noch eine zusätzliche Anforderung an die Klasse. Die Threads dürfen nicht blocken (es handelt sich um eine Anlagensteuerung) ein Lock fällt also flach denke ich.
Gruß JD

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo thinkJD,

Wie könnte ich den Zugriff koordinieren?

da gibt es keine einfache Antwort. Synchronisierung muss man beherrschen, um nicht zu sagen vollständig durchdrungen haben, um halbwegs sichere Aussagen für ein bestimmtes Szenario treffen zu können. Siehe meine Warnungen in SyncQueue <T> - Eine praktische Job-Queue. Eine Antwort in einem Forum ist da nicht ausreichend. Du musst dich selber gründlich in die Materie einarbeiten, insbesondere wenn wie bei einer Prozesssteuerung richtig Geld dranhängt. Alles andere wäre fahrlässig.

herbivore

T
thinkJD Themenstarter:in
9 Beiträge seit 2010
vor 13 Jahren

Wenn die Klasse wie im zweiten Beispiel als Singleton ausgelegt ist, kann sie doch eh nur von einem Thread gleichzeitig bearbeitet werden, oder sehe ich das falsch?

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo thinkJD,

das siehst du falsch. Alle Threads teilen sich den gleichen Datenraum. Der Name Singleton bezieht sich auf die Anzahl der Objekte, nicht auf die Anzahl der auf das eine Objekt zugreifenden Threads.

Deine Nachfrage zeigt, wie wichtig es ist, die Zusammenhänge vollständig durchdrungen zu haben. Falsche Annahmen führen zu falschem Code. Nur ist falscher Code in Multi-Threading-Programmen viel schwieriger zu finden ist. Testen alleine hilft da oft leider nicht. Deshalb ist es so wichtig, sauber zu programmieren. Das kann aber aber nur, wenn man genau weiß, was man tut. Deshalb noch einmal mein Rat: Arbeite dich sehr gründlich in das Thema ein.

herbivore

T
thinkJD Themenstarter:in
9 Beiträge seit 2010
vor 13 Jahren

Es gibt eine Instanz des Singletons. Da es threadsave ist, kann auch nur ein Thread gleichzeitig etwas damit anstellen. Die anderen werden so lange geblockt. Da die Methoden keine lange Durchlaufzeit haben, ist das zu vernachlässigen.

Wenn ich mir mit getZaehler(); ein Objekt aus der Liste hole, bekomme ich die Referenz zurück. Die kann ich dann in einem anderen Thread munter benutzen und verändern. Das Singleton selbst, bleibt nur während der Laufzeit einer Methode für die anderen Threads geblockt.

Kannst du mir an dem Satatement den Denkfehler aufzeigen?
Gruß JD

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo thinkJD,

wir reden doch über die Klasse ZaehlerAufBand, oder? Diese Klasse ist - ob nun Singleton oder nicht - überhaupt nicht threadsafe. Singletons sind nicht per se threadstafe.

herbivore

U
1.688 Beiträge seit 2007
vor 13 Jahren

Hallo,

Die Threads dürfen nicht blocken (es handelt sich um eine Anlagensteuerung) ein Lock fällt also flach denke ich.

Die anderen werden so lange geblockt. Da die Methoden keine lange Durchlaufzeit haben, ist das zu vernachlässigen.

Wie nun (bezogen auf lock und blockieren)?

Abgesehen davon zeigt Dein letzter Beitrag leider, dass Du die Problematik tatsächlich überhaupt nicht verstanden hast. Solange Resourcen/Daten gemeinsam genutzt werden, muss der Zugriff darauf geregelt werden.

Um auf die Ausgangsfrage zurückzukommen - warum meinst Du, dass Du einen Deadlock hast? Wie äußert sich dies?

Da in Deinem geposteten Code keine Synchronisationsmechanismen benutzt werden, kann es da nicht zu einem Deadlock kommen. Entweder sitzt also das Problem wo anders oder es ist ein ganz anderes Problem...