Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Tipp: Besser skalieren mit ReaderWriterGate
svenson
myCSharp.de - Member



Dabei seit:
Beiträge: 8.746
Herkunft: Berlin

Themenstarter:

Tipp: Besser skalieren mit ReaderWriterGate

beantworten | zitieren | melden

Wer mit der Skalierbarkeit von Leser-/Schreiber-Zugriffsmustern in Server-Anwendungen zu kämpfen hat, wird diesen Artikel mit Gold aufwiegen wollen:

http://msdn.microsoft.com/msdnmag/issues/06/11/ConcurrentAffairs/default.aspx?loc=de

Vorteil gegenüber dem klassischen Verfahren (ReaderWriterLock) ist das optimierte Threading mittels Queues.
private Nachricht | Beiträge des Benutzers
Traumzauberbaum
myCSharp.de - Member



Dabei seit:
Beiträge: 512

beantworten | zitieren | melden

Also wenn ich das richtig überschaue, erreicht man den selben Effekt auch deutlich einfacher.

Man braucht nur ein AutoResetEvent Objekt und die ThreadPool Funktion RegisterWaitForSingleObject.

Alle Operationen werden dann über das AutoResetEvent synchronisiert. Das erreicht man, indem man die Funktionen dann über RegisterWaitForSingleObject statt mit QueueUserWorkItem im Threadpool registriert.

Das würde schon ausreichen, um den Threadpool während einer Schreiboperation nicht mit wartenden Threads vollzumüllen. Die Queue würde dann vom ThreadPool übernommen. Das würde z.B. so aussehen:

AutoResetEvent waitObject = new AutoResetEvent(true);
// true damit erste Operation sofort ausgeführt wird

void RuftSchreibenAuf()
{
    ThreadPool.RegisterWaitForSingleObject( waitObject, WriteCallback, null, Timeout.Infinite, true );
}

void RuftLesenAuf()
{
    ThreadPool.RegisterWaitForSingleObject( waitObject, ReadCallback, null, Timeout.Infinite, true );
}

void WriteCallback( object state, bool timedOut )
{
    try {
        // hier findet das echte Schreiben statt
    }
    finally {
        waitObject.Set(); // gibt für die nächste Operation frei
    }
}

void ReadCallback( object state, bool timedOut )
{
    waitObject.Set(); // gibt die nächste Operation sofort frei

    // Lesen findet dann hier statt
    // weil vor dem eigentlichen Lesen schon freigegeben wird,
    // könnten auch mehrere Leseoperation parallel stattfinden
}

Damit gibt es noch ein Problem mit der anderen Richtung. Wenn gerade ein paar Threads fleißig am Lesen sind, und einer will was Schreiben, dann wird das nicht verhindert. Er kann einfach anfangen obwohl die Leser noch nicht fertig sind.

Das könnte man zum Beispiel mit dem ReaderWriterLock in den Griff bekommen. Im Ganzen hätte man dann maximal einen Schreibethread im Wartezustand, solange noch Leseoperationen laufen, die vorher aufgerufen wurden. Leseoperationen die nach einer Schreibeoperation aufgerufen werden, würden vom ThreadPool gequeued werden und würden keinen Thread belegen, bis der Schreiber fertig ist.

Im Ganzen würde der Code dann zum Beispiel so aussehen:

AutoResetEvent waitObject = new AutoResetEvent(true);
// true damit erste Operation sofort ausgeführt wird
ReadWriterLock readWriterLock = new ReadWriterLock();

void RuftSchreibenAuf()
{
    ThreadPool.RegisterWaitForSingleObject( waitObject, WriteCallback, null, Timeout.Infinite, true );
}

void RuftLesenAuf()
{
    ThreadPool.RegisterWaitForSingleObject( waitObject, ReadCallback, null, Timeout.Infinite, true );
}

void WriteCallback( object state, bool timedOut )
{
    readWriterLock.AcquireWriterLock( Timeout.Infinite );
    try {
        // hier findet das echte Schreiben statt
    }
    finally {
        readWriterLock.ReleaseWriterLock();
        waitObject.Set(); // gibt für die nächste Operation frei
    }
}

void ReadCallback( object state, bool timedOut )
{
    readWriterLock.AcquireReaderLock( Timeout.Infinite );
    try {
        waitObject.Set(); // gibt die nächste Operation sofort frei

        // Lesen findet dann hier statt
    }
    finally {
        readWriterLock.ReleaseReaderLock();
    }
}

Nicht wirklich sonderlich umständlich finde ich. Und den Aufwand kann man sich sparen, wenn man paralleles Lesen nicht braucht, da würde Variante 1 auch reichen, wenn man das Lesen genauso strickt wie das Schreiben (also Set() erst am Ende aufrufen). Dann würde immer nur eine Operation gleichzeitig laufen, der Rest würde über den ThreadPool gequeued werden. Ich glaube sowieso nicht so richtig, dass paralleles Lesen etwas bringen würde. Selbst mit 4 Prozessoren teilen die sich doch immernoch die gleiche Schnittstelle um auf die Festplatte zuzugreifen.

Das ist zumindest was mir in den ersten 10 Minuten dazu einfällt. Mich würde da mal eure Kritik interessieren. Scheint mir vom Ergebniss her aber ziemlich analog zu funktionieren. Man könnte bestimmt noch was verbessern, und vieleicht ists auch völliger Blödsinn.
e.f.q.

Aus Falschem folgt Beliebiges
private Nachricht | Beiträge des Benutzers
svenson
myCSharp.de - Member



Dabei seit:
Beiträge: 8.746
Herkunft: Berlin

Themenstarter:

beantworten | zitieren | melden

Bei deinem Code wird der Writer nicht priorisiert im Threadpool behandelt, wenn weitere Schreibanforderungen während einer bereits laufenden reinkommen. Bei dir bestimmt die Reihenfolge der Anfrage die Abarbeitung. Daher die händische Listenverwaltung.

Wenn man davon ausgeht, dass Lese-Anfragen aber möglichst up-to-date beantwortet werden sollen, dann ist das wünschenswert. Ist das nicht so wichtig, dann ist deine Lösung sicher um einiges schlanker.
private Nachricht | Beiträge des Benutzers
Traumzauberbaum
myCSharp.de - Member



Dabei seit:
Beiträge: 512

beantworten | zitieren | melden

Danke für die Klarstellung, das war der Teil den ich übersehen habe. Das macht es natürlich umständlicher und eine eigene Queue notwendig.
e.f.q.

Aus Falschem folgt Beliebiges
private Nachricht | Beiträge des Benutzers