Czy ktoś ma dobry zasób dotyczący wdrażania strategii puli obiektów współużytkowanych dla ograniczonego zasobu w duchu puli połączeń Sql? (tj. zostałby zaimplementowany w pełni, że jest bezpieczny dla wątków).
Aby odpowiedzieć na prośbę @Aaronaught o wyjaśnienie, użycie puli będzie dotyczyło żądań równoważenia obciążenia do usługi zewnętrznej. Ujmując to w scenariuszu, który prawdopodobnie byłby łatwiejszy do natychmiastowego zrozumienia, w przeciwieństwie do mojej bezpośredniej sytuacji. Mam obiekt sesji, który działa podobnie do ISession
obiektu z NHibernate. Że każda unikalna sesja zarządza połączeniem z bazą danych. Obecnie mam 1 długo działający obiekt sesji i napotykam problemy, w których mój dostawca usług ogranicza szybkość korzystania z tej indywidualnej sesji.
Ponieważ nie spodziewają się, że pojedyncza sesja będzie traktowana jako długotrwałe konto usługi, najwyraźniej traktują ją jako klienta, który hamuje ich usługę. Co prowadzi mnie tutaj do mojego pytania, zamiast mieć jedną indywidualną sesję, utworzyłbym pulę różnych sesji i podzielił żądania na usługę w tych wielu sesjach, zamiast tworzyć pojedynczy punkt centralny, jak robiłem wcześniej.
Miejmy nadzieję, że to tło ma jakąś wartość, ale aby bezpośrednio odpowiedzieć na niektóre pytania:
P: Czy tworzenie obiektów jest drogie?
O: Żadne obiekty nie są pulą ograniczonych zasobów
P: Czy będą często nabywane / wydawane?
Odp .: Tak, po raz kolejny można je pomyśleć o NHibernate ISessions, w których 1 jest zwykle nabywane i wydawane na czas trwania każdego żądania strony.
P: Czy wystarczy zwykła zasada „kto pierwszy, ten lepszy”, czy potrzebujesz czegoś bardziej inteligentnego, tj. Co zapobiegnie głodowi?
O: Prosta dystrybucja typu round robin byłaby wystarczająca, zakładam, że przez głód masz na myśli, że jeśli nie ma dostępnych sesji, dzwoniący zostaną zablokowani, czekając na wydania. To nie ma zastosowania, ponieważ sesje mogą być współużytkowane przez różnych rozmówców. Moim celem jest rozłożenie wykorzystania na wiele sesji, a nie na jedną sesję.
Uważam, że jest to prawdopodobnie odejście od normalnego korzystania z puli obiektów, dlatego pierwotnie pominąłem tę część i planowałem tylko dostosować wzór, aby umożliwić dzielenie się obiektami, a nie pozwolić, aby kiedykolwiek wystąpiła sytuacja głodowa.
P: A co z takimi rzeczami, jak priorytety, leniwe i chętne ładowanie itp.?
Odp .: Nie ma tu żadnego ustalania priorytetów, dla uproszczenia po prostu załóżmy, że utworzę pulę dostępnych obiektów podczas tworzenia samej puli.
źródło
Odpowiedzi:
Pule obiektów w .NET Core
Rdzeń DotNet ma realizację łączenia przedmiotów dodaje się do biblioteki klas bazowej (BCL). Możesz przeczytać oryginalny numer GitHub tutaj i wyświetlić kod dla System.Buffers . Obecnie
ArrayPool
jest jedynym dostępnym typem i służy do łączenia tablic. Jest ładny blogu tutaj .Przykład jego użycia można zobaczyć w ASP.NET Core. Ponieważ znajduje się w rdzeniu dotnet BCL, ASP.NET Core może udostępniać swoją pulę obiektów innym obiektom, takim jak serializator JSON Newtonsoft.Json. Możesz przeczytać ten wpis na blogu, aby uzyskać więcej informacji o tym, jak robi to Newtonsoft.Json.
Pule obiektów w kompilatorze Microsoft Roslyn C #
Nowy kompilator Microsoft Roslyn C # zawiera typ ObjectPool , który jest używany do gromadzenia często używanych obiektów, które normalnie byłyby bardzo często aktualizowane i zbierane jako śmieci. Zmniejsza to ilość i rozmiar operacji czyszczenia pamięci, które muszą się wydarzyć. Istnieje kilka różnych implementacji podrzędnych, z których wszystkie używają ObjectPool (patrz: Dlaczego w Roslyn jest tak wiele implementacji puli obiektów? ).
1 - SharedPools - Przechowuje pulę 20 obiektów lub 100, jeśli używana jest BigDefault.
2 - ListPool i StringBuilderPool - Nie są to ściśle oddzielne implementacje, ale otoki wokół implementacji SharedPools pokazanej powyżej, szczególnie dla List i StringBuilder's. Więc to ponownie wykorzystuje pulę obiektów przechowywanych w SharedPools.
3 - PooledDictionary i PooledHashSet - używają bezpośrednio ObjectPool i mają całkowicie oddzielną pulę obiektów. Przechowuje pulę 128 obiektów.
Microsoft.IO.RecyclableMemoryStream
Ta biblioteka zapewnia pule dla
MemoryStream
obiektów. To zastępczy zamiennikSystem.IO.MemoryStream
. Ma dokładnie tę samą semantykę. Został zaprojektowany przez inżynierów Bing. Przeczytaj wpis na blogu tutaj lub zobacz kod na GitHub .Zauważ, że
RecyclableMemoryStreamManager
powinno zostać zadeklarowane raz, a będzie działać przez cały proces - to jest pula. Jeśli chcesz, możesz używać wielu basenów.źródło
RecyclableMemoryStream
, jest to niesamowity dodatek do ultra wysokiej optymalizacji wydajności.To pytanie jest trochę trudniejsze, niż można by się spodziewać, z powodu kilku niewiadomych: zachowanie zasobu, który jest gromadzony, oczekiwany / wymagany czas życia obiektów, rzeczywisty powód, dla którego pula jest wymagana itp. Zazwyczaj pule są specjalnego przeznaczenia - wątek pule, pule połączeń itp. - ponieważ łatwiej jest je zoptymalizować, gdy wiesz dokładnie, co robi zasób, a co ważniejsze, masz kontrolę nad tym, jak ten zasób jest wdrażany.
Ponieważ nie jest to takie proste, starałem się zaproponować dość elastyczne podejście, z którym można eksperymentować i zobaczyć, co działa najlepiej. Z góry przepraszamy za długi post, ale jest wiele powodów do omówienia, jeśli chodzi o wdrożenie przyzwoitej puli zasobów ogólnego przeznaczenia. i tak naprawdę tylko drapię powierzchnię.
Pula ogólnego przeznaczenia musiałaby mieć kilka głównych „ustawień”, w tym:
W przypadku mechanizmu ładowania zasobów .NET już daje nam czystą abstrakcję - delegatów.
Prześlij to przez konstruktor puli i już z tym skończymy. Używanie typu ogólnego z
new()
ograniczeniem również działa, ale jest to bardziej elastyczne.Z pozostałych dwóch parametrów strategia dostępu jest bardziej skomplikowaną bestią, więc moje podejście polegało na zastosowaniu podejścia opartego na dziedziczeniu (interfejsie):
Koncepcja tutaj jest prosta - pozwolimy
Pool
klasie publicznej zająć się typowymi problemami, takimi jak bezpieczeństwo wątków, ale używamy innego „magazynu elementów” dla każdego wzorca dostępu. LIFO jest łatwo reprezentowane przez stos, FIFO to kolejka, a ja użyłem niezbyt zoptymalizowanej, ale prawdopodobnie wystarczającej implementacji bufora cyklicznego, używającejList<T>
wskaźnika i indeksu do przybliżenia wzorca dostępu typu round-robin.Wszystkie poniższe klasy są wewnętrznymi klasami
Pool<T>
- to był wybór stylu, ale ponieważ tak naprawdę nie są przeznaczone do użytku poza nimiPool
, ma to największy sens.To są oczywiste - stos i kolejka. Nie sądzę, żeby naprawdę uzasadniały wiele wyjaśnień. Bufor kołowy jest trochę bardziej skomplikowany:
Mogłem wybrać wiele różnych podejść, ale najważniejsze jest to, że zasoby powinny być dostępne w tej samej kolejności, w jakiej zostały utworzone, co oznacza, że musimy zachować odniesienia do nich, ale oznaczyć je jako „w użyciu” (lub nie ). W najgorszym przypadku dostępne jest tylko jedno gniazdo, a każde pobranie wymaga pełnej iteracji bufora. Jest to złe, jeśli masz zebrane setki zasobów i pozyskujesz je i zwalniasz kilka razy na sekundę; nie stanowi to problemu w przypadku puli 5-10 przedmiotów, aw typowym przypadku, gdy zasoby są słabo wykorzystywane, wystarczy przesunąć o jeden lub dwa miejsca.
Pamiętaj, te klasy są prywatnymi klasami wewnętrznymi - dlatego nie potrzebują dużo sprawdzania błędów, sama pula ogranicza do nich dostęp.
Wrzuć wyliczenie i metodę fabryczną i skończymy z tą częścią:
Następnym problemem do rozwiązania jest strategia ładowania. Zdefiniowałem trzy typy:
Pierwsze dwa powinny być oczywiste; trzeci jest rodzajem hybrydy, leniwie ładuje zasoby, ale w rzeczywistości nie zaczyna ich ponownie używać, dopóki pula nie zostanie zapełniona. Byłby to dobry kompromis, jeśli chcesz, aby pula była pełna (co brzmi tak, jakbyś to robiła), ale chcesz odłożyć koszt faktycznego utworzenia ich do pierwszego dostępu (tj. Aby poprawić czas uruchamiania).
Metody ładowania naprawdę nie są zbyt skomplikowane, teraz, gdy mamy abstrakcję sklepu z przedmiotami:
Powyższe pola
size
icount
odnoszą się odpowiednio do maksymalnego rozmiaru puli i całkowitej liczby zasobów należących do puli (ale niekoniecznie dostępnych ).AcquireEager
jest najprostszy, zakłada, że towar jest już w sklepie - elementy te zostałyby załadowane na etapie budowy, czyli wPreloadItems
sposób pokazany jako ostatni.AcquireLazy
sprawdza, czy w puli są wolne elementy, a jeśli nie, tworzy nowy.AcquireLazyExpanding
utworzy nowy zasób, o ile pula nie osiągnęła jeszcze swojego docelowego rozmiaru. Próbowałem w celu optymalizacji tego celu zminimalizowania blokowania i mam nadzieję, że nie popełniłem żadnych błędów (I zostały przetestowane w warunkach wielowątkowych, ale oczywiście nie w sposób wyczerpujący).Możesz się zastanawiać, dlaczego żadna z tych metod nie zawraca sobie głowy sprawdzaniem, czy sklep osiągnął maksymalny rozmiar. Zaraz do tego dojdę.
Teraz czas na sam basen. Oto pełny zestaw prywatnych danych, z których część została już pokazana:
Odpowiadając na pytanie, które przemilczałem w ostatnim akapicie - jak zapewnić ograniczenie całkowitej liczby tworzonych zasobów - okazuje się, że .NET ma już do tego doskonale dobre narzędzie, nazywa się Semafor i jest zaprojektowane specjalnie, aby umożliwić naprawę liczba wątków dostępu do zasobu (w tym przypadku „zasób” to wewnętrzny magazyn elementów). Ponieważ nie wdrażamy pełnej kolejki producent / konsument, jest to całkowicie adekwatne do naszych potrzeb.
Konstruktor wygląda tak:
Nie powinno tu być żadnych niespodzianek. Jedyną rzeczą, na którą należy zwrócić uwagę, jest specjalna obudowa do szybkiego ładowania, przy użyciu
PreloadItems
metody już przedstawionej wcześniej.Ponieważ prawie wszystko zostało już czysto wyabstrahowane, rzeczywistość
Acquire
iRelease
metody są naprawdę bardzo proste:Jak wyjaśniono wcześniej, używamy
Semaphore
do kontrolowania współbieżności zamiast religijnego sprawdzania statusu sklepu z przedmiotami. Dopóki zdobyte przedmioty są prawidłowo wydawane, nie ma się czym martwić.Wreszcie, jest porządek:
Cel tej
IsDisposed
własności stanie się jasny za chwilę. Jedyną głównąDispose
metodą jest usunięcie rzeczywistych pozycji w puli, jeśli są one implementowaneIDisposable
.Teraz możesz zasadniczo używać tego tak, jak jest, z
try-finally
blokiem, ale nie przepadam za tą składnią, ponieważ jeśli zaczniesz przekazywać zasoby w puli między klasami i metodami, stanie się to bardzo zagmatwane. Jest możliwe, że główne klasy, który korzysta z zasobów nawet nie mają odniesienie do basenu. Naprawdę robi się dość bałagan, więc lepszym podejściem jest utworzenie „inteligentnego” obiektu w puli.Powiedzmy, że zaczynamy od następującego prostego interfejsu / klasy:
Oto nasz udawany
Foo
zasób jednorazowego użytku, który implementujeIFoo
i ma pewien standardowy kod do generowania unikalnych tożsamości. Tworzymy kolejny specjalny obiekt w puli:To po prostu przekazuje wszystkie „prawdziwe” metody do swojego wewnętrznego
IFoo
(moglibyśmy to zrobić z biblioteką Dynamic Proxy, taką jak Castle, ale nie będę się w to zagłębiał). Utrzymuje również odniesienie do tego,Pool
który go tworzy, więc kiedy myDispose
ten obiekt, automatycznie zwalnia się z powrotem do puli. Z wyjątkiem sytuacji, gdy pula została już usunięta - oznacza to, że jesteśmy w trybie „czyszczenia” iw tym przypadku faktycznie czyści zasoby wewnętrzne .Korzystając z powyższego podejścia, możemy napisać kod w następujący sposób:
To bardzo dobra rzecz, aby móc to zrobić. Oznacza to, że kod, który wykorzystuje
IFoo
(w przeciwieństwie do kodu, który ją tworzy) faktycznie nie trzeba zdawać sobie sprawę z basenu. Możesz nawet wstrzykiwaćIFoo
obiekty przy użyciu swojej ulubionej biblioteki DI iPool<T>
dostawcy / fabryki.Umieściłem cały kod w PasteBin dla przyjemności kopiowania i wklejania. Istnieje również krótki program testowy, którego możesz użyć do zabawy z różnymi trybami ładowania / dostępu i warunkami wielowątkowymi, aby upewnić się, że jest bezpieczny dla wątków i nie zawiera błędów.
Daj mi znać, jeśli masz jakieś pytania lub wątpliwości w związku z którymkolwiek z tych tematów.
źródło
Coś takiego może odpowiadać Twoim potrzebom.
Przykładowe użycie
źródło
Put
metody i dla uproszczenia pominąłbyś pewien rodzaj sprawdzania, czy obiekt jest uszkodzony i utworzyć nową instancję, która zostanie dodana do puli zamiast wstawiania poprzedniej?Przykład z MSDN: instrukcje: tworzenie puli obiektów przy użyciu ConcurrentBag
źródło
Kiedyś firma Microsoft udostępniła strukturę za pośrednictwem Microsoft Transaction Server (MTS), a później modelu COM +, aby wykonywać pule obiektów COM. Ta funkcjonalność została przeniesiona do System.EnterpriseServices w .NET Framework, a teraz w Windows Communication Foundation.
Pula obiektów w WCF
Ten artykuł pochodzi z platformy .NET 1.1, ale nadal powinien mieć zastosowanie w bieżących wersjach platformy Framework (mimo że preferowaną metodą jest WCF).
Pule obiektów .NET
źródło
IInstanceProvider
interfejs istnieje, ponieważ zaimplementuję go w moim rozwiązaniu. Zawsze jestem fanem układania mojego kodu za interfejsem dostarczonym przez Microsoft, gdy zapewniają one pasującą definicję.Bardzo podoba mi się implementacja Aronaught - zwłaszcza, że obsługuje on czekanie na udostępnienie zasobu za pomocą semafora. Jest kilka dodatków, które chciałbym wprowadzić:
sync.WaitOne()
dosync.WaitOne(timeout)
i narazić timeout jako parametr wAcquire(int timeout)
metodzie. Wymagałoby to również obsługi warunku, w którym wątek przekroczy limit czasu oczekiwania na udostępnienie obiektu.Recycle(T item)
, aby obsłużyć sytuacje, w których obiekt musi zostać odtworzony, na przykład, gdy wystąpi awaria.źródło
To kolejna implementacja z ograniczoną liczbą obiektów w puli.
źródło
Zorientowany na Javę, ten artykuł ujawnia wzorzec puli connectionImpl i abstrakcyjny wzorzec puli obiektów i może być dobrym pierwszym podejściem: http://www.developer.com/design/article.php/626171/Pattern-Summaries-Object-Pool. htm
Wzór puli obiektów:
źródło
Rozszerzenie metody MSDN dotyczącej tworzenia puli obiektów przy użyciu ConcurrentBag.
https://github.com/chivandikwa/ObjectPool
źródło
Możesz użyć pakietu nuget
Microsoft.Extensions.ObjectPool
Dokumentacje tutaj:
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
źródło