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.

  1. Misslyckas med att tillhandahålla ett objekt (och returnerar ett fel till klienten).
  2. 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).
  3. 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

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

externa länkar