Objekt pool mönster
Objektpoolmönstret är ett mjukvaruskapande designmönster som använder en uppsättning initierade objekt som hålls redo att användas – en " pool " – snarare än att allokera och förstöra dem på begäran. En klient till poolen kommer att begära ett objekt från poolen och utföra operationer på det returnerade objektet. När klienten är klar returnerar den objektet till poolen istället för att förstöra det ; detta kan göras manuellt eller automatiskt.
Objektpooler används främst för prestanda: i vissa fall förbättrar objektpooler prestandan avsevärt. Objektpooler komplicerar objektlivslängden , eftersom objekt som erhålls från och returneras till en pool faktiskt inte skapas eller förstörs vid denna tidpunkt och därför kräver omsorg vid implementering.
Beskrivning
När det är nödvändigt att arbeta med ett flertal objekt som är särskilt dyra att instansiera och varje objekt bara behövs under en kort tidsperiod, kan prestandan för en hel applikation påverkas negativt. Ett designmönster för objektpooler kan anses önskvärt i fall som dessa.
Objektpoolens designmönster skapar en uppsättning objekt som kan återanvändas. När ett nytt objekt behövs efterfrågas det från poolen. Om ett tidigare förberett objekt är tillgängligt, returneras det omedelbart, vilket undviker instansieringskostnaden. Om inga objekt finns i poolen skapas ett nytt objekt och returneras. När objektet har använts och inte längre behövs, returneras det till poolen, vilket gör att det kan användas igen i framtiden utan att den beräkningsmässigt dyra instansieringsprocessen upprepas. Det är viktigt att notera att när ett objekt har använts och returnerats kommer befintliga referenser att bli ogiltiga.
I vissa objektpooler är resurserna begränsade, så ett maximalt antal objekt anges. Om detta nummer uppnås och ett nytt föremål begärs, kan ett undantag kastas, eller så kommer tråden att blockeras tills ett föremål släpps tillbaka i poolen.
Objektpoolens designmönster används på flera ställen i standardklasserna för .NET Framework. Ett exempel är .NET Framework Data Provider för SQL Server. Eftersom SQL Server-databasanslutningar kan vara långsamma att skapa, upprätthålls en pool av anslutningar. Att stänga en anslutning ger faktiskt inte upp länken till SQL Server. Istället hålls anslutningen i en pool, från vilken den kan hämtas när man begär en ny anslutning. Detta ökar avsevärt hastigheten för att skapa anslutningar.
Fördelar
Objektpoolning kan erbjuda en betydande prestandaökning i situationer där kostnaden för att initiera en klassinstans är hög och hastigheten för instansiering och förstörelse av en klass är hög – i det här fallet kan objekt ofta återanvändas, och varje återanvändning sparar en betydande mängd tid. Objektpooling kräver resurser – minne och möjligen andra resurser, såsom nätverkssockets, och därför är det att föredra att antalet instanser som används vid en viss tidpunkt är lågt, men detta krävs inte.
Det poolade objektet erhålls inom förutsägbar tid när skapandet av de nya objekten (särskilt över nätverk) kan ta varierande tid. Dessa fördelar gäller mestadels för objekt som är dyra med avseende på tid, såsom databasanslutningar, socketanslutningar, trådar och stora grafiska objekt som typsnitt eller bitmappar.
I andra situationer kanske enkel objektpoolning (som inte innehåller några externa resurser, utan bara upptar minne) inte är effektiv och kan minska prestandan. Vid enkel minnespoolning för skivallokering mer lämpad, eftersom det enda målet är att minimera kostnaden för minnesallokering och -deallokering genom att minska fragmenteringen.
Genomförande
Objektpooler kan implementeras på ett automatiserat sätt i språk som C++ via smarta pekare . I den smarta pekarens konstruktor kan ett objekt begäras från poolen, och i den smarta pekarens destruktor kan objektet släppas tillbaka till poolen. måste objektpooler implementeras manuellt, genom att uttryckligen begära ett objekt från fabriken och returnera objektet genom att anropa en avyttringsmetod (som i kasseringsmönstret ) . Att använda en finalizer för att göra detta är inte en bra idé, eftersom det vanligtvis inte finns några garantier för när (eller om) finalizern kommer att köras. Istället bör "försök ... äntligen" användas för att säkerställa att få och släppa objektet är undantagsneutralt.
Manuella objektpooler är enkla att implementera, men svårare att använda, eftersom de kräver manuell minneshantering av poolobjekt.
Hantering av tomma pooler
Objektpooler använder en av tre strategier för att hantera en begäran när det inte finns några reservobjekt i poolen.
- Misslyckas med att tillhandahålla ett objekt (och returnerar ett fel till klienten).
- Tilldela ett nytt objekt och öka storleken på poolen. Pooler som gör detta låter dig vanligtvis ställa in högvattenmärket (det maximala antalet föremål som någonsin använts).
- I en flertrådsmiljö kan en pool blockera klienten tills en annan tråd returnerar ett objekt till poolen.
Fallgropar
Försiktighet måste iakttas för att säkerställa att tillståndet för objekten som returneras till poolen återställs till ett förnuftigt tillstånd för nästa användning av objektet, annars kan objektet vara i ett tillstånd som klienten oväntat, vilket kan leda till att det misslyckas. Poolen ansvarar för att återställa objekten, inte klienterna. Objektpooler fulla av objekt med farligt inaktuellt tillstånd kallas ibland objektavloppspooler och betraktas som ett antimönster .
Inaktuellt tillstånd är kanske inte alltid ett problem; det blir farligt när det får föremålet att bete sig oväntat. Till exempel kan ett objekt som representerar autentiseringsdetaljer misslyckas om flaggan "framgångsrikt autentiserad" inte återställs innan den återanvänds, eftersom den indikerar att en användare är autentiserad (möjligen som någon annan) när de inte är det. Men att misslyckas med att återställa ett värde som endast används för felsökning, till exempel identiteten för den senast använda autentiseringsservern, kan dock inte innebära några problem.
Otillräcklig återställning av objekt kan orsaka informationsläckor. Objekt som innehåller konfidentiella uppgifter (t.ex. en användares kreditkortsnummer) måste rensas innan de skickas till nya kunder, annars kan uppgifterna lämnas ut till en obehörig part.
Om poolen används av flera trådar kan den behöva hjälpmedel för att förhindra parallella trådar från att försöka återanvända samma objekt parallellt. Detta är inte nödvändigt om de poolade objekten är oföränderliga eller på annat sätt trådsäkra.
Kritik
Vissa publikationer rekommenderar inte att du använder objektpoolning med vissa språk, till exempel Java , särskilt för objekt som bara använder minne och inte innehåller några externa resurser (som anslutningar till databasen). Motståndare brukar säga att objektallokering är relativt snabb i moderna språk med sophämtare ; medan den nya operatören
bara behöver tio instruktioner, kräver det klassiska nya
- raderingsparet
som finns i pooldesigner hundratals av dem eftersom det utför mer komplext arbete. Dessutom skannar de flesta sophämtare "live" objektreferenser, och inte minnet som dessa objekt använder för sitt innehåll. Detta innebär att valfritt antal "döda" objekt utan referenser kan kasseras med liten kostnad. Att hålla ett stort antal "live" men oanvända föremål däremot ökar varaktigheten av sophämtningen.
Exempel
Gå
Följande Go-kod initierar en resurspool av en specificerad storlek (samtidig initiering) för att undvika resursraceproblem genom kanaler, och i fallet med en tom pool, ställer in timeoutbearbetning för att förhindra att klienter väntar för länge.
0
0
// paketpool paketpoolimport ( "errors" "log" "math/rand" " sync" " time" ) const getResMaxTime = 3 * tid . Second var ( ErrPoolNotExist = errors . New ( "pool not exist" ) ErrGetResTimeout = errors . New ( "get resurs timeout" ) ) // Resurstyp Resursstruktur { resId int } //NewResource Simulera långsam resursinitiering // ( t.ex. TCP-anslutning, förvärv av symmetrisk SSL-nyckel, autentisering är tidskrävande) func NewResource ( id int ) * Resurs { tid . Sleep ( 500 * tid . Millisekunder ) return & Resource { resId : id } } //Do Simuleringsresurser är tidskrävande och slumpmässig förbrukning är 0~400ms func ( r * Resource ) Do ( workId int ) { time . Sömn ( tid . Varaktighet ( rand . Intn ( 5 )) * 100 * tid . Millisekunder ) logg . Printf ( "använder resurs #%d avslutat arbete %d avsluta\n" , r . resId , workId ) } // Pool baserat på Go-kanalimplementering, för att undvika resursrace tillstånd problemtyp Pool chan * Resurs // Ny en resurspool av den angivna storleken // Resurser skapas samtidigt för att spara resursinitieringstid func New ( storlek int ) Pool { p := make ( Pool , storlek ) wg := new ( sync . WaitGroup ) wg . Lägg till ( storlek ) för i := ; i < storlek ; i ++ { go func ( resId int ) { p <- NewResource ( resId ) wg . Klar () }( i ) } wg . Vänta () returnera p } //GetResource baserat på kanal, resursracingstillstånd undviks och resursförvärvningstimeout är inställd för tom pool func ( p Pool ) GetResource ( ) ( r * Resource , err error ) { select { case r := <- p : return r , noll fall <- tid . Efter ( getResMaxTime ): return nil , ErrGetResTimeout } } // GiveBackResource returnerar resurser till resurspoolen func ( p Pool ) GiveBackResource ( r * Resource ) error { if p == nil { return ErrPoolNotExist } p <- r return nil } / paket huvudpaket huvudimport ( " github.com/tkstorm/go-design/creational/object-pool/pool" "log" " sync" ) func main () { // Initiera en pool med fem resurser, // som kan justeras till 1 eller 10 för att se skillnaden storlek := 5 p := pool . Ny ( storlek ) // Anropar en resurs för att utföra id-jobbet doWork := func ( workId int , wg * sync . WaitGroup ) { defer wg . Klar () // Hämta resursen från resurspoolen res , err := p . GetResource () if err != noll { log . Println ( err ) return } // Resurser att returnera defer p . GiveBackResource ( res ) // Använd resurser för att hantera arbetsres . Gör ( workId ) } // Simulera 100 samtidiga processer för att få resurser från tillgångspoolen num := 100 wg := new ( sync . WaitGroup ) wg . Lägg till ( num ) för i := ; i < num ; i ++ { go doWork ( i , wg ) } wg . Vänta () }
C#
I .NET Base Class Library finns det några objekt som implementerar detta mönster. System.Threading.ThreadPool
är konfigurerad att ha ett fördefinierat antal trådar att tilldela. När trådarna returneras är de tillgängliga för en annan beräkning. Således kan man använda trådar utan att betala kostnaden för att skapa och avyttra trådar.
Följande visar den grundläggande koden för objektpoolens designmönster implementerat med C#. För korthetens skull deklareras klassernas egenskaper med C# 3.0 automatiskt implementerad egenskapssyntax. Dessa kan ersättas med fullständiga egenskapsdefinitioner för tidigare versioner av språket. Pool visas som en statisk klass, eftersom det är ovanligt att flera pooler krävs. Det är dock lika acceptabelt att använda instansklasser för objektpooler.
0
0
0
namnutrymme DesignPattern.Objectpool ; // PooledObject-klassen är den typ som är dyr eller långsam att instansiera, // eller som har begränsad tillgänglighet, så den ska hållas i objektpoolen. public class PooledObject { private DateTime _createdAt = DateTime . Nu ; public DateTime CreatedAt { get { return _createdAt ; } } offentlig sträng TempData { get ; set ; } } // Poolklassen styr åtkomsten till de poolade objekten. Den upprätthåller en lista över tillgängliga objekt och en // samling av objekt som har erhållits från poolen och som används. Poolen säkerställer att släppta föremål // återförs till lämpligt tillstånd, redo för återanvändning. public static class Pool { private static List < PooledObject > _available = new List < PooledObject >(); privat statisk lista < PooledObject > _inUse = ny lista < PooledObject >(); public static PooledObject GetObject () { lock ( _available ) { if ( _available . Count != ) { PooledObject po = _available [ ]; _inUse . Lägg till ( po ); _tillgänglig . RemoveAt ( ); returnera po ; } annat { PooledObject po = nytt PooledObject (); _inUse . Lägg till ( po ); returnera po ; } } } public static void ReleaseObject ( PooledObject po ) { CleanUp ( po ); lås ( _available ) { _available . Lägg till ( po ); _inUse . Ta bort ( po ); } } privat statisk void CleanUp ( PooledObject po ) { po . TempData = null ; } }
I koden ovan har PooledObject egenskaper för den tid det skapades, och en annan, som kan ändras av klienten, som återställs när PooledObject släpps tillbaka till poolen. Visas är saneringsprocessen, när ett objekt släpps, vilket säkerställer att det är i ett giltigt tillstånd innan det kan begäras från poolen igen.
Java
Java stöder trådpoolning via java.util.concurrent.ExecutorService
och andra relaterade klasser. Exekutortjänsten har ett visst antal "grundläggande" trådar som aldrig kasseras. Om alla trådar är upptagna allokerar tjänsten det tillåtna antalet extra trådar som senare kasseras om de inte används under den bestämda utgångstiden. Om inga fler trådar tillåts kan uppgifterna placeras i kön. Slutligen, om den här kön kan bli för lång, kan den konfigureras för att avbryta den begärande tråden.
public class PooledObject { public String temp1 ; public String temp2 ; public String temp3 ; public String getTemp1 () { return temp1 ; } public void setTemp1 ( String temp1 ) { this . temp1 = temp1 ; } public String getTemp2 () { return temp2 ; } public void setTemp2 ( String temp2 ) { this . temp2 = temp2 ; } public String getTemp3 () { return temp3 ; } public void setTemp3 ( String temp3 ) { this . temp3 = temp3 ; } }
public class PooledObjectPool { private static long expTime = 6000 ; //6 sekunder offentlig statisk HashMap < PooledObject , Long > tillgänglig = new HashMap < PooledObject , Long > (); public static HashMap < PooledObject , Long > inUse = new HashMap < PooledObject , Long > (); public synchronized static PooledObject getObject () { long now = System . currentTimeMillis (); if ( ! tillgänglig . isEmpty ()) { for ( Karta . Entry < PooledObject , Long > entry : available . entrySet ()) { if ( now - entry . getValue () > expTime ) { //object has expired popElement ( tillgängligt ); } else { PooledObject po = popElement ( tillgängligt , entry . getKey ()); push ( inUse , po , nu ); returnera po ; } } } // antingen finns inget PooledObject tillgängligt eller så har alla gått ut, så returnera en ny return createPooledObject ( nu ); } privat synkroniserat statiskt PooledObject createPooledObject ( länge nu ) { PooledObject po = nytt PooledObject (); push ( inUse , po , nu ); returnera po ; } privat synkroniserad statisk void push ( HashMap < PooledObject , Long > map , PooledObject po , long now ) { map . sätta ( po , nu ); } public static void releaseObject ( PooledObject po ) { cleanUp ( po ); tillgängligt . put ( po , System . currentTimeMillis ()); inUse . ta bort ( po ); } privat statisk PooledObject popElement ( HashMap < PooledObject , Long > map ) { Map . Entry < PooledObject , Long > entry = map . entrySet (). iterator (). nästa (); PooledObject- nyckel = post . getKey (); //Långt värde=entry.getValue(); karta . remove ( entry . getKey ()); returnyckel ; _ } privat statisk PooledObject popElement ( HashMap < PooledObject , Long > map , PooledObject key ) { map . ta bort ( nyckel ); returnyckel ; _ } public static void cleanUp ( PooledObject po ) { po . setTemp1 ( null ); po . setTemp2 ( null ); po . setTemp3 ( null ); } }
Se även
Anteckningar
- Kircher, Michael; Prashant Jain (2002-07-04). "Poolingmönster" (PDF) . EuroPLoP 2002 . Tyskland . Hämtad 2007-06-09 .
- Goldshtein, Sasha; Zurbalev, Dima; Flatow, Ido (2012). Pro .NET-prestanda: Optimera dina C#-applikationer . Apress. ISBN 978-1-4302-4458-5 .
externa länkar
- OODesign artikel
- Förbättra prestanda med objektpoolning (Microsoft Developer Network)
- Developer.com artikel
- Portland Pattern Repository post
- Apache Commons Pool: Ett mini-ramverk för att korrekt implementera objektpoolning i Java
- Spelprogrammeringsmönster: Objektpool