Hat jemand eine gute Ressource für die Implementierung einer gemeinsamen Objektpoolstrategie für eine begrenzte Ressource im Sinne des SQL-Verbindungspoolings? (dh würde vollständig implementiert, dass es threadsicher ist).
Um die @ Klaronaught-Anfrage zur Klärung zu verfolgen, würde die Poolnutzung für Lastausgleichsanforderungen an einen externen Dienst verwendet. Um es in ein Szenario zu bringen, das im Gegensatz zu meiner direkten Situation wahrscheinlich leichter sofort zu verstehen wäre. Ich habe ein Sitzungsobjekt, das ähnlich wie das ISession
Objekt von NHibernate funktioniert. Dass jede einzelne Sitzung ihre Verbindung zur Datenbank verwaltet. Derzeit habe ich 1 Sitzungsobjekt mit langer Laufzeit und es treten Probleme auf, bei denen mein Dienstanbieter die Nutzung dieser einzelnen Sitzung durch eine Rate einschränkt.
Aufgrund ihrer mangelnden Erwartung, dass eine einzelne Sitzung als ein langjähriges Dienstkonto behandelt wird, behandeln sie es anscheinend als einen Kunden, der ihren Dienst hämmert. Das bringt mich zu meiner Frage hier: Anstatt eine einzelne Sitzung zu haben, würde ich einen Pool verschiedener Sitzungen erstellen und die Anforderungen auf den Dienst auf mehrere Sitzungen aufteilen, anstatt wie zuvor einen einzelnen Schwerpunkt zu erstellen.
Hoffentlich bietet dieser Hintergrund einen gewissen Wert, aber um einige Ihrer Fragen direkt zu beantworten:
F: Sind die Objekte teuer in der Erstellung?
A: Keine Objekte sind ein Pool begrenzter Ressourcen
F: Werden sie sehr häufig erworben / freigegeben?
A: Ja, wieder einmal können sie an NHibernate ISessions denken, bei denen 1 normalerweise für die Dauer jeder einzelnen Seitenanforderung erworben und freigegeben wird.
F: Wird ein einfaches Wer zuerst kommt, mahlt zuerst oder brauchen Sie etwas Intelligenteres, das den Hunger verhindern würde?
A: Eine einfache Round-Robin-Verteilung würde ausreichen. Ich gehe davon aus, dass Sie meinen, wenn keine Sitzungen verfügbar sind, Anrufer blockiert werden und auf Freigaben warten. Dies ist nicht wirklich zutreffend, da die Sitzungen von verschiedenen Anrufern gemeinsam genutzt werden können. Mein Ziel ist es, die Nutzung auf mehrere Sitzungen zu verteilen, im Gegensatz zu einer einzelnen Sitzung.
Ich glaube, dies ist wahrscheinlich eine Abweichung von der normalen Verwendung eines Objektpools, weshalb ich diesen Teil ursprünglich weggelassen und geplant habe, nur das Muster anzupassen, um das Teilen von Objekten zu ermöglichen, anstatt jemals eine Hungersituation zuzulassen.
F: Was ist mit Dingen wie Prioritäten, faulem oder eifrigem Laden usw.?
A: Es ist keine Priorisierung erforderlich. Nehmen Sie der Einfachheit halber einfach an, dass ich den Pool verfügbarer Objekte bei der Erstellung des Pools selbst erstellen würde.
quelle
Antworten:
Objektpooling in .NET Core
Der Dotnet-Kern verfügt über eine Implementierung von Objektpooling, die der Basisklassenbibliothek (BCL) hinzugefügt wurde. Sie können das ursprüngliche GitHub-Problem hier lesen und den Code für System.Buffers anzeigen . Derzeit
ArrayPool
ist der einzige verfügbare Typ und wird zum Poolen von Arrays verwendet. Es gibt eine schöne Blog - Post hier .Ein Beispiel für die Verwendung finden Sie in ASP.NET Core. Da es sich in der Dotnet Core-BCL befindet, kann ASP.NET Core seinen Objektpool für andere Objekte wie den JSON-Serializer von Newtonsoft.Json freigeben. In diesem Blogbeitrag finden Sie weitere Informationen dazu, wie Newtonsoft.Json dies tut.
Objektpooling im Microsoft Roslyn C # -Compiler
Der neue Microsoft Roslyn C # -Compiler enthält den ObjectPool- Typ, mit dem häufig verwendete Objekte zusammengefasst werden, die normalerweise neu erstellt und sehr häufig Müll gesammelt werden. Dies reduziert die Menge und Größe der Garbage Collection-Vorgänge, die stattfinden müssen. Es gibt einige verschiedene Unterimplementierungen, die alle ObjectPool verwenden (siehe: Warum gibt es in Roslyn so viele Implementierungen von Object Pooling? ).
1 - SharedPools - Speichert einen Pool von 20 Objekten oder 100, wenn BigDefault verwendet wird.
2 - ListPool und StringBuilderPool - Keine streng getrennten Implementierungen, sondern Wrapper um die oben gezeigte SharedPools-Implementierung, speziell für List und StringBuilder. Dadurch wird der in SharedPools gespeicherte Objektpool wiederverwendet.
3 - PooledDictionary und PooledHashSet - Diese verwenden ObjectPool direkt und haben einen völlig separaten Pool von Objekten. Speichert einen Pool von 128 Objekten.
Microsoft.IO.RecyclableMemoryStream
Diese Bibliothek bietet Pooling für
MemoryStream
Objekte. Es ist ein Ersatz fürSystem.IO.MemoryStream
. Es hat genau die gleiche Semantik. Es wurde von Bing-Ingenieuren entworfen. Lesen Sie den Blog-Beitrag hier oder sehen Sie sich den Code auf GitHub an .Beachten Sie, dass
RecyclableMemoryStreamManager
dies einmal deklariert werden sollte und für den gesamten Prozess gültig ist - dies ist der Pool. Es ist vollkommen in Ordnung, mehrere Pools zu verwenden, wenn Sie dies wünschen.quelle
RecyclableMemoryStream
dass dies eine erstaunliche Ergänzung für Optimierungen mit ultrahoher Leistung ist.Diese Frage ist aufgrund mehrerer Unbekannter etwas kniffliger als erwartet: Das Verhalten der zu bündelnden Ressource, die erwartete / erforderliche Lebensdauer von Objekten, der wahre Grund, warum der Pool erforderlich ist usw. In der Regel handelt es sich bei Pools um Spezialthreads Pools, Verbindungspools usw. - weil es einfacher ist, einen zu optimieren, wenn Sie genau wissen, was die Ressource tut, und vor allem die Kontrolle darüber haben, wie diese Ressource implementiert wird.
Da es nicht so einfach ist, habe ich versucht, einen ziemlich flexiblen Ansatz anzubieten, mit dem Sie experimentieren und sehen können, was am besten funktioniert. Wir entschuldigen uns im Voraus für den langen Beitrag, aber es gibt viel zu tun, wenn es um die Implementierung eines angemessenen Ressourcenpools für allgemeine Zwecke geht. und ich kratzte wirklich nur an der Oberfläche.
Ein Allzweckpool müsste einige wichtige "Einstellungen" haben, darunter:
Für den Mechanismus zum Laden von Ressourcen bietet uns .NET bereits eine saubere Abstraktion - Delegaten.
Führen Sie dies durch den Konstruktor des Pools und wir sind damit fertig. Die Verwendung eines generischen Typs mit einer
new()
Einschränkung funktioniert ebenfalls, dies ist jedoch flexibler.Von den beiden anderen Parametern ist die Zugriffsstrategie das kompliziertere Biest, daher bestand mein Ansatz darin, einen auf Vererbung (Schnittstelle) basierenden Ansatz zu verwenden:
Das Konzept hier ist einfach: Wir lassen die öffentliche
Pool
Klasse die allgemeinen Probleme wie die Thread-Sicherheit behandeln, verwenden jedoch für jedes Zugriffsmuster einen anderen "Item Store". LIFO kann leicht durch einen Stapel dargestellt werden, FIFO ist eine Warteschlange, und ich habe eine nicht sehr optimierte, aber wahrscheinlich adäquate Ringpufferimplementierung verwendet, die einenList<T>
Indexzeiger verwendet, um ein Round-Robin-Zugriffsmuster zu approximieren.Alle unten aufgeführten Klassen sind innere Klassen der
Pool<T>
- dies war eine Stilwahl, aber da diese wirklich nicht außerhalb der Klasse verwendet werden sollenPool
, ist dies am sinnvollsten.Dies sind die offensichtlichen - Stapel und Warteschlange. Ich denke nicht, dass sie wirklich viel Erklärung verdienen. Der Ringpuffer ist etwas komplizierter:
Ich hätte verschiedene Ansätze wählen können, aber unter dem Strich sollte auf Ressourcen in derselben Reihenfolge zugegriffen werden, in der sie erstellt wurden. Dies bedeutet, dass wir Verweise auf sie beibehalten, sie jedoch als "in Verwendung" markieren müssen (oder nicht) ). Im schlimmsten Fall ist immer nur ein Steckplatz verfügbar, und für jeden Abruf ist eine vollständige Iteration des Puffers erforderlich. Dies ist schlecht, wenn Sie Hunderte von Ressourcen gepoolt haben und diese mehrmals pro Sekunde erwerben und freigeben. Nicht wirklich ein Problem für einen Pool von 5-10 Elementen, und im typischen Fall, wenn Ressourcen nur wenig genutzt werden, müssen nur ein oder zwei Slots vorgerückt werden.
Denken Sie daran, dass diese Klassen private innere Klassen sind. Deshalb müssen sie nicht viel auf Fehler überprüft werden. Der Pool selbst beschränkt den Zugriff auf sie.
Wenn Sie eine Aufzählung und eine Factory-Methode eingeben, sind wir mit diesem Teil fertig:
Das nächste zu lösende Problem ist die Ladestrategie. Ich habe drei Typen definiert:
Die ersten beiden sollten selbsterklärend sein; Der dritte ist eine Art Hybrid, der Ressourcen faul lädt, aber erst dann wieder Ressourcen verwendet, wenn der Pool voll ist. Dies wäre ein guter Kompromiss, wenn Sie möchten, dass der Pool voll ist (wie es sich anhört), aber die Kosten für die tatsächliche Erstellung bis zum ersten Zugriff aufschieben möchten (dh um die Startzeiten zu verbessern).
Die Lademethoden sind wirklich nicht zu kompliziert, jetzt, wo wir die Item-Store-Abstraktion haben:
Die obigen Felder
size
undcount
beziehen sich auf die maximale Größe des Pools und die Gesamtzahl der Ressourcen, die dem Pool gehören (aber nicht unbedingt verfügbar sind ).AcquireEager
ist die einfachste, es wird davon ausgegangen, dass sich ein Artikel bereits im Geschäft befindet - diese Artikel würden bei der Erstellung vorgeladen, dh in derPreloadItems
zuletzt gezeigten Methode.AcquireLazy
Überprüft, ob sich freie Elemente im Pool befinden. Andernfalls wird ein neues erstellt.AcquireLazyExpanding
erstellt eine neue Ressource, solange der Pool seine Zielgröße noch nicht erreicht hat. Ich habe versucht, dies zu optimieren, um das Sperren zu minimieren, und ich hoffe, ich habe keine Fehler gemacht (ich habe dies unter Multithread-Bedingungen getestet, aber offensichtlich nicht vollständig).Sie fragen sich möglicherweise, warum bei keiner dieser Methoden überprüft wird, ob das Geschäft die maximale Größe erreicht hat oder nicht. Ich werde gleich darauf zurückkommen.
Nun zum Pool selbst. Hier ist der vollständige Satz privater Daten, von denen einige bereits angezeigt wurden:
Bei der Beantwortung der Frage, die ich im letzten Absatz beschönigt habe - wie man sicherstellt, dass wir die Gesamtzahl der erstellten Ressourcen begrenzen - stellt sich heraus, dass .NET bereits ein perfektes Tool dafür hat, es heißt Semaphore und wurde speziell entwickelt, um eine feste Lösung zu ermöglichen Anzahl der Threads, die auf eine Ressource zugreifen (in diesem Fall ist die "Ressource" der innere Objektspeicher). Da wir keine vollständige Warteschlange für Produzenten / Konsumenten implementieren, ist dies für unsere Anforderungen vollkommen ausreichend.
Der Konstruktor sieht folgendermaßen aus:
Sollte hier keine Überraschungen geben. Zu beachten ist nur das Spezialgehäuse für eifriges Laden mit der
PreloadItems
bereits zuvor gezeigten Methode.Da inzwischen fast alles sauber abstrahiert wurde, sind die tatsächlichen
Acquire
undRelease
Methoden wirklich sehr einfach:Wie bereits erläutert, verwenden wir das
Semaphore
, um die Parallelität zu steuern, anstatt den Status des Item-Stores religiös zu überprüfen. Solange erworbene Gegenstände korrekt freigegeben werden, besteht kein Grund zur Sorge.Last but not least gibt es Aufräumarbeiten:
Der Zweck dieser
IsDisposed
Eigenschaft wird in einem Moment klar. Die HauptmethodeDispose
besteht darin, die tatsächlich gepoolten Elemente zu entsorgen, wenn sie implementiert werdenIDisposable
.Jetzt können Sie dies im Grunde genommen so wie es ist mit einem
try-finally
Block verwenden, aber ich mag diese Syntax nicht, denn wenn Sie anfangen, gepoolte Ressourcen zwischen Klassen und Methoden weiterzugeben, wird es sehr verwirrend. Es ist möglich , dass die Hauptklasse , die eine Ressource nutzt nicht einmal hat einen Verweis auf den Pool. Es wird wirklich ziemlich chaotisch, daher ist es besser, ein "intelligentes" gepooltes Objekt zu erstellen.Angenommen, wir beginnen mit der folgenden einfachen Schnittstelle / Klasse:
Hier ist unsere vorgetäuschte verfügbare
Foo
Ressource, dieIFoo
einen Boilerplate-Code zum Generieren eindeutiger Identitäten implementiert und enthält. Wir erstellen ein weiteres spezielles, gepooltes Objekt:Dies überträgt nur alle "echten" Methoden auf das Innere
IFoo
(wir könnten dies mit einer Dynamic Proxy-Bibliothek wie Castle tun, aber darauf werde ich nicht eingehen). Es wird auch ein Verweis auf das Objekt beibehalten, dasPool
es erstellt, sodass es sich beiDispose
diesem Objekt automatisch wieder in den Pool zurückgibt. Außer wenn der Pool bereits entsorgt wurde - dies bedeutet, dass wir uns im "Bereinigungs" -Modus befinden und in diesem Fall stattdessen die interne Ressource bereinigt wird .Mit dem obigen Ansatz können wir Code wie folgt schreiben:
Das ist eine sehr gute Sache. Es bedeutet , dass der Code, der verwendet die
IFoo
(im Gegensatz zu dem Code, der es schafft) nicht wirklich bewusst , den Pool sein muß. Sie können Objekte sogar mit Ihrer bevorzugten DI-Bibliothek und als Provider / Factory injizieren .IFoo
Pool<T>
Ich habe den vollständigen Code für das Kopieren und Einfügen in PasteBin eingefügt . Es gibt auch ein kurzes Testprogramm, mit dem Sie mit verschiedenen Lade- / Zugriffsmodi und Multithread-Bedingungen herumspielen können, um sich davon zu überzeugen, dass es threadsicher und nicht fehlerhaft ist.
Lassen Sie mich wissen, wenn Sie Fragen oder Bedenken dazu haben.
quelle
So etwas könnte Ihren Bedürfnissen entsprechen.
Beispiel Verwendung
quelle
Put
Methode behandeln und es der Einfachheit halber weglassen, um zu überprüfen, ob das Objekt fehlerhaft ist, und um eine neue Instanz zu erstellen, die dem Pool hinzugefügt werden soll, anstatt die vorherige einzufügen?Beispiel aus MSDN: Gewusst wie: Erstellen eines Objektpools mithilfe eines ConcurrentBag
quelle
Früher stellte Microsoft über Microsoft Transaction Server (MTS) und später COM + ein Framework für das Objekt-Pooling für COM-Objekte bereit. Diese Funktionalität wurde in .NET Framework und jetzt in Windows Communication Foundation auf System.EnterpriseServices übertragen.
Objektpooling in WCF
Dieser Artikel stammt aus .NET 1.1, sollte jedoch in den aktuellen Versionen des Frameworks weiterhin gelten (obwohl WCF die bevorzugte Methode ist).
Objektpooling .NET
quelle
IInstanceProvider
Schnittstelle vorhanden ist, da ich dies für meine Lösung implementieren werde. Ich bin immer ein Fan davon, meinen Code hinter einer von Microsoft bereitgestellten Oberfläche zu stapeln, wenn sie eine passende Definition bieten.Ich mag die Implementierung von Aronaught sehr - vor allem, weil er das Warten auf die Verfügbarkeit von Ressourcen mithilfe eines Semaphors übernimmt. Es gibt mehrere Ergänzungen, die ich machen möchte:
sync.WaitOne()
zusync.WaitOne(timeout)
und die Timeout als Parameter auf belichtenAcquire(int timeout)
Methode. Dies würde auch die Behandlung der Bedingung erforderlich machen, wenn der Thread eine Zeitüberschreitung aufweist und darauf wartet, dass ein Objekt verfügbar wird.Recycle(T item)
Methode hinzu, um Situationen zu behandeln, in denen ein Objekt beispielsweise bei einem Fehler recycelt werden muss.quelle
Dies ist eine weitere Implementierung mit einer begrenzten Anzahl von Objekten im Pool.
quelle
Dieser Artikel ist Java-orientiert und zeigt das Poolmuster connectionImpl und das Poolmuster abstrahierter Objekte. Dies könnte ein guter erster Ansatz sein: http://www.developer.com/design/article.php/626171/Pattern-Summaries-Object-Pool. htm
Objektpool Muster:
quelle
Eine Erweiterung von msdn zum Erstellen eines Objektpools mit einem ConcurrentBag.
https://github.com/chivandikwa/ObjectPool
quelle
Sie können das Nuget-Paket verwenden
Microsoft.Extensions.ObjectPool
Dokumentationen hier:
https://docs.microsoft.com/en-us/aspnet/core/performance/objectpool?view=aspnetcore-3.1 https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.objectpool
quelle