Quelqu'un a-t-il une bonne ressource sur la mise en œuvre d'une stratégie de pool d'objets partagés pour une ressource limitée dans la veine de la mise en commun de connexions SQL? (c'est-à-dire serait pleinement implémenté qu'il est thread-safe).
Pour faire le suivi de la demande de clarification @Aaronaught, l'utilisation du pool serait pour les demandes d'équilibrage de charge à un service externe. Pour le mettre dans un scénario qui serait probablement plus facile à comprendre immédiatement par opposition à ma situation directe. J'ai un objet de session qui fonctionne de manière similaire à l' ISession
objet de NHibernate. Que chaque session unique gère sa connexion à la base de données. Actuellement, j'ai 1 objet de session de longue durée et je rencontre des problèmes où mon fournisseur de services limite mon utilisation de cette session individuelle.
En raison de leur manque d'espoir qu'une seule session serait traitée comme un compte de service de longue durée, ils la traitent apparemment comme un client qui martèle leur service. Ce qui m'amène à ma question ici, au lieu d'avoir 1 session individuelle, je créerais un pool de sessions différentes et répartirais les demandes au service sur ces multiples sessions au lieu de créer un seul point focal comme je le faisais auparavant.
Espérons que ce contexte offre une certaine valeur, mais pour répondre directement à certaines de vos questions:
Q: Les objets sont-ils coûteux à créer?
R: Aucun objet n'est un pool de ressources limitées
Q: Seront-ils acquis / libérés très fréquemment?
R: Oui, une fois de plus, ils peuvent être considérés comme NHibernate ISessions où 1 est généralement acquis et publié pour la durée de chaque demande de page.
Q: Un simple premier arrivé, premier servi suffira-t-il ou avez-vous besoin de quelque chose de plus intelligent, c'est-à-dire qui éviterait la famine?
R: Une simple distribution de type round robin suffirait, par famine, je suppose que vous voulez dire s'il n'y a pas de sessions disponibles, les appelants sont bloqués en attendant les versions. Ce n'est pas vraiment applicable car les sessions peuvent être partagées par différents appelants. Mon objectif est de répartir l'utilisation sur plusieurs sessions au lieu d'une seule session.
Je crois que c'est probablement une divergence par rapport à une utilisation normale d'un pool d'objets, c'est pourquoi j'ai initialement laissé cette partie de côté et prévu simplement d'adapter le modèle pour permettre le partage d'objets au lieu de permettre à une situation de famine de se produire.
Q: Qu'en est-il des choses comme les priorités, le chargement paresseux ou impatient, etc.?
R: Il n'y a pas de hiérarchisation, par souci de simplicité, supposons simplement que je créerais le pool d'objets disponibles lors de la création du pool lui-même.
la source
Réponses:
Regroupement d'objets dans .NET Core
Le noyau dotnet a une implémentation du pool d'objets ajoutée à la bibliothèque de classes de base (BCL). Vous pouvez lire le problème GitHub d'origine ici et afficher le code pour System.Buffers . Actuellement, il
ArrayPool
s'agit du seul type disponible et utilisé pour regrouper les baies. Il y a un bon article de blog ici .Un exemple de son utilisation peut être vu dans ASP.NET Core. Comme il se trouve dans le BCL dotnet core, ASP.NET Core peut partager son pool d'objets avec d'autres objets tels que le sérialiseur JSON de Newtonsoft.Json. Vous pouvez lire ce billet de blog pour plus d'informations sur la façon dont Newtonsoft.Json fait cela.
Regroupement d'objets dans le compilateur Microsoft Roslyn C #
Le nouveau compilateur Microsoft Roslyn C # contient le type ObjectPool , qui est utilisé pour regrouper les objets fréquemment utilisés qui seraient normalement remis en état et récupérés très souvent. Cela réduit la quantité et la taille des opérations de récupération de place qui doivent se produire. Il existe quelques sous-implémentations différentes utilisant toutes ObjectPool (voir: Pourquoi y a-t-il autant d'implémentations de pool d'objets dans Roslyn? ).
1 - SharedPools - Stocke un pool de 20 objets ou 100 si BigDefault est utilisé.
2 - ListPool et StringBuilderPool - Des implémentations non strictement séparées mais des enveloppes autour de l'implémentation SharedPools illustrée ci-dessus spécifiquement pour List et StringBuilder. Donc, cela réutilise le pool d'objets stockés dans SharedPools.
3 - PooledDictionary et PooledHashSet - Ceux-ci utilisent ObjectPool directement et ont un pool d'objets totalement séparé. Stocke un pool de 128 objets.
Microsoft.IO.RecyclableMemoryStream
Cette bibliothèque permet de regrouper les
MemoryStream
objets. C'est un remplacement instantané pourSystem.IO.MemoryStream
. Il a exactement la même sémantique. Il a été conçu par les ingénieurs de Bing. Lisez l'article de blog ici ou consultez le code sur GitHub .Notez que cela
RecyclableMemoryStreamManager
devrait être déclaré une fois et qu'il vivra pendant tout le processus - c'est le pool. Il est parfaitement bien d'utiliser plusieurs piscines si vous le souhaitez.la source
RecyclableMemoryStream
c'est un ajout étonnant pour les optimisations ultra hautes performances.Cette question est un peu plus délicate que ce à quoi on pourrait s'attendre en raison de plusieurs inconnues: le comportement de la ressource mise en pool, la durée de vie attendue / requise des objets, la vraie raison pour laquelle le pool est requis, etc. pools, pools de connexions, etc. - car il est plus facile d'en optimiser un lorsque vous savez exactement ce que fait la ressource et, plus important encore, contrôlez la manière dont cette ressource est mise en œuvre.
Comme ce n'est pas si simple, ce que j'ai essayé de faire est de proposer une approche assez flexible que vous pouvez expérimenter et voir ce qui fonctionne le mieux. Toutes mes excuses à l'avance pour le long message, mais il y a beaucoup de chemin à parcourir pour mettre en œuvre un pool de ressources à usage général décent. et je ne fais que gratter la surface.
Une piscine à usage général devrait avoir quelques "paramètres" principaux, notamment:
Pour le mécanisme de chargement des ressources, .NET nous donne déjà une abstraction propre - les délégués.
Passez ceci à travers le constructeur de la piscine et nous en avons presque terminé. L'utilisation d'un type générique avec une
new()
contrainte fonctionne également, mais c'est plus flexible.Parmi les deux autres paramètres, la stratégie d'accès est la bête la plus compliquée, donc mon approche consistait à utiliser une approche basée sur l'héritage (interface):
Le concept ici est simple - nous laisserons la
Pool
classe publique gérer les problèmes courants tels que la sécurité des threads, mais utiliserons un "magasin d'objets" différent pour chaque modèle d'accès. LIFO est facilement représenté par une pile, FIFO est une file d'attente, et j'ai utilisé une implémentation de tampon circulaire pas très optimisée mais probablement adéquate en utilisant unList<T>
pointeur d'index et pour approximer un modèle d'accès circulaire .Toutes les classes ci-dessous sont des classes internes de
Pool<T>
- c'était un choix de style, mais comme elles ne sont vraiment pas destinées à être utilisées en dehors dePool
, cela a le plus de sens.Ce sont les plus évidents - pile et file d'attente. Je ne pense pas qu'ils méritent vraiment beaucoup d'explications. Le tampon circulaire est un peu plus compliqué:
J'aurais pu choisir un certain nombre d'approches différentes, mais l'essentiel est que les ressources doivent être accessibles dans le même ordre où elles ont été créées, ce qui signifie que nous devons conserver les références à celles-ci, mais les marquer comme "en cours d'utilisation" (ou non ). Dans le pire des cas, un seul emplacement est toujours disponible, et il faut une itération complète du tampon pour chaque extraction. C'est mauvais si vous avez des centaines de ressources mises en commun et que vous les acquérez et les libérez plusieurs fois par seconde; ce n'est pas vraiment un problème pour un pool de 5 à 10 éléments, et dans le cas typique , où les ressources sont peu utilisées, il suffit d'avancer d'un ou deux emplacements.
N'oubliez pas que ces classes sont des classes internes privées - c'est pourquoi elles n'ont pas besoin de beaucoup de vérification d'erreurs, le pool lui-même en restreint l'accès.
Ajoutez une énumération et une méthode d'usine et nous en avons terminé avec cette partie:
Le prochain problème à résoudre est la stratégie de chargement. J'ai défini trois types:
Les deux premiers devraient être explicites; le troisième est une sorte d'hybride, il charge les ressources paresseusement mais ne commence pas à réutiliser les ressources tant que le pool n'est pas plein. Ce serait un bon compromis si vous voulez que la piscine soit pleine (ce qui semble être le cas) mais que vous voulez reporter les frais de création effective jusqu'au premier accès (c'est-à-dire pour améliorer les temps de démarrage).
Les méthodes de chargement ne sont vraiment pas trop compliquées, maintenant que nous avons l'abstraction du magasin d'objets:
Les champs
size
etcount
ci-dessus font référence respectivement à la taille maximale du pool et au nombre total de ressources détenues par le pool (mais pas nécessairement disponibles ).AcquireEager
est le plus simple, il suppose qu'un article est déjà dans le magasin - ces articles seraient préchargés à la construction, c'est-à-dire dans laPreloadItems
méthode indiquée en dernier.AcquireLazy
vérifie s'il y a des articles gratuits dans le pool, et sinon, il en crée un nouveau.AcquireLazyExpanding
créera une nouvelle ressource tant que le pool n'a pas encore atteint sa taille cible. J'ai essayé d'optimiser ce pour minimiser le verrouillage, et j'espère que je ne l' ai pas fait d'erreur (je l' ai testé dans des conditions multi-thread, mais évidemment pas exhaustive).Vous vous demandez peut-être pourquoi aucune de ces méthodes ne se soucie de vérifier si le magasin a atteint ou non la taille maximale. J'y reviendrai dans un instant.
Maintenant pour la piscine elle-même. Voici l'ensemble complet des données privées, dont certaines ont déjà été montrées:
En réponse à la question que j'ai passée sous silence dans le dernier paragraphe - comment nous assurer de limiter le nombre total de ressources créées - il s'avère que le .NET dispose déjà d'un très bon outil pour cela, il s'appelle Semaphore et il est spécifiquement conçu pour permettre un nombre de threads accédant à une ressource (dans ce cas, la "ressource" est le magasin d'objets interne). Puisque nous n'implémentons pas une file d'attente complète de producteurs / consommateurs, cela répond parfaitement à nos besoins.
Le constructeur ressemble à ceci:
Ne devrait pas y avoir de surprises ici. La seule chose à noter est le boîtier spécial pour un chargement hâtif, en utilisant la
PreloadItems
méthode déjà montrée précédemment.Étant donné que presque tout a été proprement abrégé maintenant, le réel
Acquire
et lesRelease
méthodes sont vraiment très simples:Comme expliqué précédemment, nous utilisons le
Semaphore
pour contrôler la concurrence au lieu de vérifier religieusement l'état du magasin d'objets. Tant que les objets acquis sont correctement libérés, il n'y a rien à craindre.Dernier point mais non le moindre, il y a le nettoyage:
Le but de cette
IsDisposed
propriété deviendra clair dans un instant. Tout ce que laDispose
méthode principale fait vraiment est de supprimer les éléments mis en commun s'ils sont implémentésIDisposable
.Maintenant, vous pouvez essentiellement l'utiliser tel
try-finally
quel , avec un bloc, mais je n'aime pas cette syntaxe, car si vous commencez à faire passer des ressources mises en commun entre les classes et les méthodes, cela deviendra très déroutant. Il est possible que la classe principale qui utilise une ressource ne même pas avoir une référence à la piscine. Cela devient vraiment assez compliqué, donc une meilleure approche consiste à créer un objet groupé «intelligent».Disons que nous commençons avec l'interface / classe simple suivante:
Voici notre prétendue
Foo
ressource jetable qui implémenteIFoo
et contient un code standard pour générer des identités uniques. Ce que nous faisons est de créer un autre objet spécial et groupé:Cela renvoie simplement toutes les méthodes «réelles» à son intérieur
IFoo
(nous pourrions le faire avec une bibliothèque Dynamic Proxy comme Castle, mais je n'entrerai pas dans cela). Il conserve également une référence à celuiPool
qui le crée, de sorte que lorsque nousDispose
cet objet, il se libère automatiquement dans le pool. Sauf lorsque le pool a déjà été éliminé - cela signifie que nous sommes en mode "nettoyage" et dans ce cas, il nettoie en fait la ressource interne à la place.En utilisant l'approche ci-dessus, nous arrivons à écrire du code comme celui-ci:
C'est une très bonne chose à pouvoir faire. Cela signifie que le code qui utilise le
IFoo
(par opposition au code qui le crée) n'a pas réellement besoin de connaître le pool. Vous pouvez même injecter desIFoo
objets en utilisant votre bibliothèque DI préférée et enPool<T>
tant que fournisseur / usine.J'ai mis le code complet sur PasteBin pour votre plaisir de copier-coller. Il existe également un court programme de test que vous pouvez utiliser pour jouer avec différents modes de chargement / accès et des conditions multithreads, pour vous assurer qu'il est thread-safe et non bogué.
Faites-moi savoir si vous avez des questions ou des préoccupations à ce sujet.
la source
Quelque chose comme ça pourrait répondre à vos besoins.
Exemple d'utilisation
la source
Put
méthode et laisser de côté pour plus de simplicité un certain type de vérification de si l'objet est défectueux et de créer une nouvelle instance à ajouter au pool au lieu d'insérer la précédente?Exemple de MSDN: Comment: créer un pool d'objets à l'aide d'un ConcurrentBag
la source
À l'époque, Microsoft fournissait un cadre via Microsoft Transaction Server (MTS) et plus tard COM + pour effectuer le regroupement d'objets pour les objets COM. Cette fonctionnalité a été transférée à System.EnterpriseServices dans le .NET Framework et maintenant dans Windows Communication Foundation.
Regroupement d'objets dans WCF
Cet article provient de .NET 1.1 mais doit toujours s'appliquer dans les versions actuelles du Framework (même si WCF est la méthode préférée).
Regroupement d'objets .NET
la source
IInstanceProvider
interface existe car je vais l'implémenter pour ma solution. Je suis toujours fan de l'empilement de mon code derrière une interface fournie par Microsoft lorsqu'ils fournissent une définition appropriée.J'aime vraiment l'implémentation d'Aronaught - d'autant plus qu'il gère l'attente de la disponibilité des ressources grâce à l'utilisation d'un sémaphore. Il y a plusieurs ajouts que je voudrais faire:
sync.WaitOne()
desync.WaitOne(timeout)
et exposer le délai d' attente en tant que paramètre sur laAcquire(int timeout)
méthode. Cela nécessiterait également de gérer la condition lorsque le thread expire en attendant qu'un objet devienne disponible.Recycle(T item)
méthode pour gérer les situations où un objet doit être recyclé en cas de panne, par exemple.la source
Il s'agit d'une autre implémentation, avec un nombre limité d'objets dans le pool.
la source
Orienté Java, cet article expose le modèle de pool connectionImpl et le modèle de pool d'objets abstrait et pourrait être une bonne première approche: http://www.developer.com/design/article.php/626171/Pattern-Summaries-Object-Pool. htm
Modèle de pool d'objets:
la source
Une extension de msdn comment créer un pool d'objets à l'aide d'un ConcurrentBag.
https://github.com/chivandikwa/ObjectPool
la source
Vous pouvez utiliser le package nuget
Microsoft.Extensions.ObjectPool
Documentations ici:
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
la source