Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Nox

Supermoderator

  • »Nox« ist der Autor dieses Themas

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

1

06.04.2008, 00:36

Queue im Eigenbau

Hi, ich beschäftige mich nun schon seit einiger Zeit mit Threadprogrammierung. Dabei ist mir aufgefallen, dass die Kommunikation über gelockte Listen zwar eine Option ist, aber doch teilweise massive Latenzprobleme herbeiführt. Daher habe ich nach einem pattern oder schon fertigen Klassen geschaut, um die Kommunikation zwischen Threads sicher und schnell zu machen. Ich habe auch einiges gefunden, aber nichts konnte mich so wirklich überzeugen. Also habe ich schnell was eigenes zusammengeschribselt. Nun bin ich mir aber nicht so ganz sicher, ob ich alle Fälle abgesichert habe und ob es nicht vielleicht noch eleganter geht.

Lange Rede, kurzer Sinn:
Ich bitte um Bewertung, Hinweise auf Lücken und Verbesserungsvorschläge

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
template <class T> class SecureStack
{   
    struct Entity
    {
        T       val;
        Entity* next;

        Entity(T _val) : val(_val), next(0) { }
        ~Entity()                           { }
    };
    Entity* Head;

    SecureStack(const SecureStack&)         { }//forbitten

    void operator = (const SecureStack&)    { }//forbitten


public:
    class ReturnType
    {
        Entity* pointer;

    public:
        ReturnType(void) : pointer(0)       { }
        ReturnType(Entity* e) : pointer(e)  { }
        ReturnType(ReturnType& e)           { pointer = e.pointer; e.pointer = 0; }
        ~ReturnType()                       { delete pointer; }
        bool operator = (ReturnType& e)     { delete pointer; pointer = e.pointer; e.pointer = 0; return (pointer != NULL); }
        T& operator*(void)                  { return pointer->val; }
        T* operator ->(void)                { return &pointer->val; }
    };

    SecureStack(void) : Head(0) { }
    ~SecureStack()              { while(Head) { Entity* p = Head; Head = Head->next; delete p; } }
    

    bool empty(void) const      { return (Head == 0); }

    bool getAll(std::list<T> &list)
    {
        //wir ersetzen Head  einfach gegen NULL und behalten Head

#pragma warning( disable: 4312)
#pragma warning( disable: 4311)
        Entity* oldH = (Entity*)InterlockedExchangePointer((void**)&Head, NULL);
#pragma warning( default: 4312)
#pragma warning( default: 4311)
        
        while(oldH)
        {
            list.push_front(oldH->val);
            Entity* h = oldH;
            oldH = oldH->next;
            delete h;
        }
        return true;
    }

    ReturnType pop(void) //not very fast

    {
        Entity* h = Head; 
        if(!h) return NULL;

        //auch wenn h ungültig werden sollte, so gibt es keinen Segfault, 

        //weil h->next ohne Zeigerverscheibung abläuft,

        //welche zum Absturz führen würden


        //wir tauschen Head und seinen Nachfolger einfach aus und behalten Head

        Entity* oldH = (Entity*)InterlockedCompareExchangePointer((void**)&Head, h->next, h);

        if(oldH != h)   return 0; //da hat einer dazwischengefunkt

        else            return oldH;//alles i.o.

    }

    void push(const T &val) 
    {
        Entity* oldH, *h;
        Entity* n = new Entity(val);
        
        do
        {
            h       = Head;
            n->next = h;
            oldH    = (Entity*)InterlockedCompareExchangePointer((void**)&Head, n, h);
        }while(oldH != h);
    }   
};
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

David_pb

Community-Fossil

Beiträge: 3 886

Beruf: 3D Graphics Programmer

  • Private Nachricht senden

2

06.04.2008, 19:11

Was ist mit dem Kopierverhalten?
@D13_Dreinig

Nox

Supermoderator

  • »Nox« ist der Autor dieses Themas

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

3

06.04.2008, 19:23

Hmm der Fall tritt bei meinem aktuellen Projekt nie auf, aber du hast durchaus recht. Wenn man diese Klasse allgemeingültig gestalten wollte, wäre dieser von Nöten. Aber irgendwie bezweifel ich, dass es Bedarf für eine allgemeine Gestaltung gibt. Zumindest nach der Resonanz hier zu schließen :) . Wenn es jedoch Bedarf geben sollte, könnte ich den gerne nachreichen.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

David_pb

Community-Fossil

Beiträge: 3 886

Beruf: 3D Graphics Programmer

  • Private Nachricht senden

4

06.04.2008, 19:26

Du sagst jetzt, das der Fall bei dir nie auftritt. Der Fall tritt aber schneller mal auf als man denken sollte! :) Dann deklarier den Kopierkonstruktor und Zuweissungsoperator wenigstens privat, dann ist es auch noch korrekt.
Ansonsten würd ich lieber "critical sections" für die Synchronisation verwenden statt einem Haufen atomarer Funktionen.
@D13_Dreinig

Nox

Supermoderator

  • »Nox« ist der Autor dieses Themas

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

5

06.04.2008, 19:44

Zitat

...Dabei ist mir aufgefallen, dass die Kommunikation über gelockte Listen zwar eine Option ist, aber doch teilweise massive Latenzprobleme herbeiführt...


;)

Grundproblematik ist ja, dass ein Container, der mittels CS abgesichert ist, zwar sehr sicher sein kann, allerdings auch dazu führt, dass die Threads Wartezeiten von über 50 ms hinnehmen müssen (im Falle unsere Projektes). Diese Werte waren und sind für unser Projekt nicht akzeptabel. Leider habe ich in z.b. boost nichts gefunden, was einer threadsicheren Queue entspricht.
Daher der Eigenbau mit atomic_ops.
Die Frage ist halt, ob ich potenzielle Fehler übersehen habe und ob es vielleicht irgendwo doch schon eine fertige (und bessere) Lösung für das Problem gibt.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

Nox

Supermoderator

  • »Nox« ist der Autor dieses Themas

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

6

21.04.2008, 20:41

Sooo. Überarbeitet Version, allerdings ist es nur noch ein Stack und kein Queue. Wer ne Idee hat, was man noch verbessern könnte immer her damit :)
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

7

22.04.2008, 00:02

Dein Code fuer Pop sieht anders als auf: click me

Quellcode

1
2
3
4
5
6
7
8
public void Pop() {
    ListNode newHead, origHead;
    do { // This does not work if we allow inserts in the list
        origHead = head;
        newHead = origHead.next;
    } while (CompareExchange<ListNode>(
        ref head, newHead, origHead) != origHead);
}


Aus eigener Erfahrung: Ich bin immer ganz gut mit Mutex und Semaphore gefahren, aber nur unter Linux. Wenn Threadkommunikation ein Flaschenhals ist, so verringe doch den Kommunikationsaufwand.

Zu deiner Stackimplementation: Ich sehe irgendwie nicht den Sinn von ReturnType.

Nox

Supermoderator

  • »Nox« ist der Autor dieses Themas

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

8

22.04.2008, 09:56

Hi, danke für den Link. Den kannte ich noch nicht.
Zum pop: Ja, aber der Unterschied besteht nur darin, dass die Implementation auf der Seite "um jeden Preis" versucht das pop auszuführen (Schleife), während mein Ansatz etwas naiver ist und nach dem Muster vorgeht "naja; wenn nicht heute, dann halt morgen".

Weniger Aufwand ist immer ein Zeichen für ein gutes Programmdesign. Leider habe ich allerdings keine Ideen mehr, wie ich den Aufwand weiter minimieren kann. Ein weiteres Problem mit CS/Mutex usw. ist auch, dass es immer wieder mal zu Dauerlocks kommt, weil die Iteratoren der STL(MS-impl) im Debugmodus ebenfalls Locks durchführen.

Die Returntypestruktur habe ich eingeführt, um sicher zu stellen, dass die Entityinstanzen auch wirklich wieder freigeben werden. Alternativen (mit den Nachteilen) sind: Kopie vom gespeicherten Objekt per new T(_val) holen (vergleichweise sehr langsam), Enitity als Referenz zuückgeben (benötigt pro Zuweisung einen Kopiervorgang für T, was bei großen Klassen langsam ist), Enitity als Pointer zurückgeben (was voraussetzt, dass der Programmierer weiß, dass er den Pointer freigeben muss).

Schematisch habe ich folgende "Transportwege":
ArbeitsThreads -> DatenElemente (sind Speicher für die Ergebnisse)
Arbeitsthreads -> TransferThread (erhält eine Liste von geänderten DatenElementen)
DatenElemente -> TransferThread (holt sich die Datensätze aus den DatenElementen)
TransferThread -> ArbeitsThread (bekommt Anweisungen)

Ich sehe da wenig Spielraum, weil ich einerseits Informationen in die Arbeitsthreads rein- und auch wieder rausgekommen muss. Die Trennung zwischen Datensätzen und den Hinweis, dass sich an diesen was geändert hat, ist notwendig. Gründe: Es handelt es sich um mehrere 1000, so dass ein Durchsuchen pro Durchgang sehr aufwendig für den TransferThread wäre. Außerdem weiß der TransferThread nicht, welche geänderten Datensätze wirklich benötigt werden. Dieses Wissen erhalten die ArbeitsThreads durch recht aufwendige Berechnungen.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

Werbeanzeige