Wzór puli obiektów

Wzorzec puli obiektów jest wzorcem projektowym tworzenia oprogramowania , który wykorzystuje zestaw zainicjowanych obiektów gotowych do użycia – „ pulę ” – zamiast przydzielać je i niszczyć na żądanie. Klient puli zażąda obiektu z puli i wykona operacje na zwróconym obiekcie. Kiedy klient skończy, zwraca obiekt do puli zamiast go niszczyć ; można to zrobić ręcznie lub automatycznie.

Pule obiektów są używane głównie do zwiększania wydajności: w pewnych okolicznościach pule obiektów znacząco poprawiają wydajność. Pule obiektów komplikują czas życia obiektów , ponieważ obiekty uzyskiwane z puli i zwracane do puli nie są faktycznie tworzone ani niszczone w tym czasie, a zatem wymagają starannej implementacji.

Opis

Gdy konieczna jest praca z wieloma obiektami, których utworzenie jest szczególnie kosztowne, a każdy obiekt jest potrzebny tylko przez krótki okres czasu, może to niekorzystnie wpłynąć na wydajność całej aplikacji. Wzorzec projektowy puli obiektów może być uznany za pożądany w takich przypadkach.

Wzorzec projektowy puli obiektów tworzy zestaw obiektów, które mogą być ponownie wykorzystane. Gdy potrzebny jest nowy obiekt, jest on żądany z puli. Jeśli dostępny jest wcześniej przygotowany obiekt, jest on natychmiast zwracany, co pozwala uniknąć kosztów tworzenia instancji. Jeśli w puli nie ma żadnych obiektów, tworzony jest i zwracany nowy element. Kiedy obiekt został użyty i nie jest już potrzebny, jest zwracany do puli, co pozwala na ponowne użycie go w przyszłości bez powtarzania kosztownego obliczeniowo procesu tworzenia instancji. Należy pamiętać, że po użyciu i zwróceniu obiektu istniejące referencje stracą ważność.

W niektórych pulach obiektów zasoby są ograniczone, dlatego określona jest maksymalna liczba obiektów. Jeśli ta liczba zostanie osiągnięta i zażądany zostanie nowy element, może zostać zgłoszony wyjątek lub wątek zostanie zablokowany do czasu zwolnienia obiektu z powrotem do puli.

Wzorzec projektowy puli obiektów jest używany w kilku miejscach w standardowych klasach .NET Framework. Jednym z przykładów jest dostawca danych .NET Framework dla programu SQL Server. Ponieważ tworzenie połączeń z bazą danych programu SQL Server może być powolne, utrzymywana jest pula połączeń. Zamknięcie połączenia nie oznacza w rzeczywistości rezygnacji z łącza do programu SQL Server. Zamiast tego połączenie jest utrzymywane w puli, z której można je odzyskać podczas żądania nowego połączenia. Znacząco zwiększa to szybkość wykonywania połączeń.

Korzyści

Łączenie obiektów może zapewnić znaczny wzrost wydajności w sytuacjach, gdy koszt inicjalizacji instancji klasy jest wysoki, a szybkość tworzenia instancji i niszczenia klasy jest wysoka — w takim przypadku obiekty mogą być często ponownie wykorzystywane, a każde ponowne użycie pozwala zaoszczędzić znaczną ilość czas. Pule obiektów wymagają zasobów – pamięci i ewentualnie innych zasobów, takich jak gniazda sieciowe, dlatego preferowane jest, aby liczba instancji używanych w danym momencie była niska, ale nie jest to wymagane.

Obiekt w puli jest uzyskiwany w przewidywalnym czasie, podczas gdy tworzenie nowych obiektów (zwłaszcza w sieci) może zająć zmienny czas. Korzyści te dotyczą głównie obiektów kosztownych w czasie, takich jak połączenia z bazami danych, połączenia gniazd, wątki i duże obiekty graficzne, takie jak czcionki lub mapy bitowe.

W innych sytuacjach proste łączenie obiektów (które nie przechowuje żadnych zasobów zewnętrznych, ale zajmuje tylko pamięć) może nie być wydajne i może obniżyć wydajność. W przypadku prostego łączenia pamięci, z alokacją płyt jest bardziej odpowiednia, ponieważ jedynym celem jest zminimalizowanie kosztów alokacji i zwalniania pamięci poprzez zmniejszenie fragmentacji.

Realizacja

Pule obiektów można implementować w sposób zautomatyzowany w językach takich jak C++ za pomocą inteligentnych wskaźników . W konstruktorze inteligentnego wskaźnika można zażądać obiektu z puli, aw destruktorze inteligentnego wskaźnika obiekt można zwolnić z powrotem do puli. W językach z wyrzucaniem elementów bezużytecznych, w których nie ma destruktorów (które z pewnością zostaną wywołane jako część rozwijania stosu), pule obiektów muszą być implementowane ręcznie, poprzez jawne żądanie obiektu z fabryki i zwrócenie obiektu przez wywołanie metody usuwania (jak we wzorcu usuwania ). Używać finalizatora , aby to zrobić, nie jest dobrym pomysłem, ponieważ zwykle nie ma gwarancji, kiedy (lub czy) finalizator zostanie uruchomiony. Zamiast tego należy użyć polecenia „spróbuj… w końcu”, aby upewnić się, że pobieranie i zwalnianie obiektu jest neutralne pod względem wyjątków.

Ręczne pule obiektów są proste do wdrożenia, ale trudniejsze w użyciu, ponieważ wymagają ręcznego zarządzania pamięcią obiektów puli.

Postępowanie z pustymi basenami

Pule obiektów wykorzystują jedną z trzech strategii do obsługi żądania, gdy w puli nie ma obiektów zapasowych.

  1. Niepowodzenie w dostarczeniu obiektu (i zwrócenie błędu do klienta).
  2. Przydziel nowy obiekt, zwiększając w ten sposób rozmiar puli. Baseny, które to robią, zwykle pozwalają ustawić znak wysokiej wody (maksymalna liczba obiektów, jakie kiedykolwiek zostały użyte).
  3. W środowisku wielowątkowym pula może blokować klienta, dopóki inny wątek nie zwróci obiektu do puli.

Pułapki

Należy uważać, aby stan obiektów zwróconych do puli został zresetowany z powrotem do rozsądnego stanu do następnego użycia obiektu, w przeciwnym razie obiekt może znajdować się w stanie nieoczekiwanym przez klienta, co może spowodować jego awarię. Pula jest odpowiedzialna za resetowanie obiektów, a nie klientów. Pule obiektów pełne obiektów o niebezpiecznie nieaktualnym stanie są czasami nazywane szambami obiektów i uważane za antywzorzec .

Nieaktualny stan nie zawsze może stanowić problem; staje się niebezpieczny, gdy powoduje nieoczekiwane zachowanie obiektu. Na przykład obiekt reprezentujący szczegóły uwierzytelnienia może się nie powieść, jeśli flaga „uwierzytelnienie pomyślnie” nie zostanie zresetowana przed ponownym użyciem, ponieważ wskazuje, że użytkownik jest uwierzytelniony (prawdopodobnie jako ktoś inny), gdy tak nie jest. Jednak niepowodzenie zresetowania wartości używanej tylko do debugowania, takiej jak tożsamość ostatnio używanego serwera uwierzytelniania, może nie powodować problemów.

Nieodpowiednie zresetowanie obiektów może spowodować wycieki informacji. Przedmioty zawierające poufne dane (np. numery kart kredytowych użytkownika) muszą zostać oczyszczone przed przekazaniem ich nowym klientom, w przeciwnym razie mogą zostać ujawnione osobom nieupoważnionym.

Jeśli pula jest używana przez wiele wątków, może potrzebować środków zapobiegających równoległym próbom ponownego użycia tego samego obiektu równolegle. Nie jest to konieczne, jeśli obiekty w puli są niezmienne lub w inny sposób bezpieczne wątkowo.

Krytyka

Niektóre publikacje nie zalecają używania puli obiektów w niektórych językach, takich jak Java , zwłaszcza w przypadku obiektów, które używają tylko pamięci i nie przechowują żadnych zasobów zewnętrznych (takich jak połączenia z bazą danych). Przeciwnicy zwykle twierdzą, że alokacja obiektów jest stosunkowo szybka we współczesnych językach z modułami zbierającymi elementy bezużyteczne ; podczas gdy operator new potrzebuje tylko dziesięciu instrukcji, klasyczny new - delete pair znaleziona w projektach puli wymaga ich setek, ponieważ wykonuje bardziej złożoną pracę. Ponadto większość wyrzucaczy elementów bezużytecznych skanuje „żywe” odniesienia do obiektów, a nie pamięć używaną przez te obiekty do ich zawartości. Oznacza to, że dowolną liczbę „martwych” obiektów bez odniesień można odrzucić niewielkim kosztem. Natomiast przechowywanie dużej liczby „żywych”, ale nieużywanych obiektów wydłuża czas usuwania elementów bezużytecznych.

Przykłady

Iść

Poniższy kod Go inicjuje pulę zasobów o określonym rozmiarze (inicjowanie współbieżne), aby uniknąć problemów z wyścigiem zasobów przez kanały, aw przypadku pustej puli ustawia limit czasu przetwarzania, aby uniemożliwić klientom zbyt długie oczekiwanie.


 

 
	
	
	
	
	


     

 
	   
	   // pula pakietów  import  puli  pakietów  (  "errors"  "log"  "math/rand"  "sync"  "time"  )  const  getResMaxTime  =  3  *  time  .  Druga zmienna  (  ErrPoolNotExist  =  błędy  .  Nowy  (  pula nie istnieje”  )  ErrGetResTimeout  =  błędy  .  Nowy  ( 



   
	 




    
	  
	 "get resource time out"  )  )  //  Typ zasobu   Resource  struct  {  resId  int  }  // NewResource Symulacja powolnego tworzenia inicjalizacji zasobów  // (np. połączenie TCP, pozyskiwanie klucza symetrycznego SSL, autoryzacja są czasochłonne)  func  NewResource  (  id  int  )  *  Zasób  {  czas  .  Sen  (  czas  500  *  .  Milisekunda  )  powrót   



     
	     &  Resource  {  resId  :  id  }  }  // Do Zasoby symulacji są czasochłonne, a zużycie losowe wynosi 0~400 ms  func  (  r  *  Resource  )  Do  (  workId  int  )  {  time  .  Uśpienie  (  czas  .  Czas trwania  (  rand  .  Intn  (  5  ))  *  100  *  czas  .  Milisekunda  ) 
	  



   



    
	  dziennik  .  Printf  (  "używając zasobu #%d zakończono pracę %d zakończ\n"  ,  r  .  resId  ,  workId  )  }  // Pula oparta na implementacji kanału Go, aby uniknąć problemu z wyścigiem zasobów  typu  Pool  chan  *  Resource  // Nowa pula zasobów określonego rozmiaru  // Zasoby są tworzone współbieżnie, aby zaoszczędzić czas inicjalizacji zasobów  func  New  (  size  int  )  Pool  {  p  :=   
	  
	
	   0     
		   
			  
			 make  (  pula  ,  rozmiar  )  wg  :=  new  (  sync  .  WaitGroup  )  wg  .  Dodaj  (  rozmiar  )  dla  i  :=  ;  i  <  rozmiar  ;  i  ++  {  go  func  (  resId  int  )  {  p  <-  NewResource  (  resId  )  wg  .  Gotowe  () 
		
	
	
	 



        
	 
	   
		   }(  ja  )  }  wg  .  Czekaj  ()  return  p  }  //GetResource na podstawie kanału, unika się stanu wyścigu zasobów i ustawia się limit czasu pozyskiwania zasobów dla pustej puli  func  (  p  Pool  )  GetResource  ()  (  r  *  Resource  ,  err  error  )  {  select  {  case  r  :=  <-  p  :  zwróć  r  ,  zero 
	 
		  
	



      
	    
		 
	
	  
	 przypadek  <-  czas  .  After  (  getResMaxTime  ):  return  nil  ,  ErrGetResTimeout  }  }  //GiveBackResource zwraca zasoby do puli zasobów  func  (  p  Pool  )  GiveBackResource  (  r  *  Resource  )  error  {  if  p  ==  nil  {  return  ErrPoolNotExist  }  p  <-  r  return  



 

 
	
	
	


  
	
	
	  
	  

	
	  nil  }  // pakiet main  package  main  import  (  "github.com/tkstorm/go-design/creational/object-pool/pool"  "log"  "sync"  )  func  main  ()  {  // Inicjalizacja puli pięciu zasobów,  // które można dostosować do 1 lub 10, aby zobaczyć różnicę  size  :=  5  p  :=  pool  .  New  (  size  )  // Wywołuje zasób w celu wykonania zadania id  doWork  :=      
		 
		
		   
		    
			
			
		
		
		  func  (  workId  int  ,  wg  *  sync  .  WaitGroup  )  {  odroczyć  wg  .  Gotowe  ()  // Pobierz zasób z puli zasobów  res  ,  err  :=  p  .  GetResource  ()  jeśli  err  !=  nil  {  log  .  Println  (  err  )  return  }  // Zasoby do zwrócenia  odroczenie  p  . 
		
		
	

	
	  
	  
	
	   0      GiveBackResource  (  res  )  // Użyj zasobów do obsługi pracy  res  .  Do  (  workId  )  }  // Symulacja 100 współbieżnych procesów w celu pobrania zasobów z puli zasobów  num  :=  100  wg  :=  new  (  Sync  .  WaitGroup  )  wg  .  Dodaj  (  num  )  dla  i  :=  ;  i  <  liczba  ;  ja  ++  { 
		  
	
	
 idź  doWork  (  i  ,  wg  )  }  wg  .  Czekaj  ()  } 

C#

W bibliotece .NET Base Class Library istnieje kilka obiektów, które implementują ten wzorzec. System.Threading.ThreadPool jest skonfigurowany tak, aby mieć wstępnie zdefiniowaną liczbę wątków do przydzielenia. Po zwróceniu wątków są one dostępne do innego obliczenia. W ten sposób można używać wątków bez ponoszenia kosztów tworzenia i usuwania wątków.

Poniżej przedstawiono podstawowy kod wzorca projektowego puli obiektów zaimplementowanego przy użyciu języka C#. Dla zwięzłości właściwości klas są deklarowane przy użyciu automatycznie zaimplementowanej składni właściwości języka C# 3.0. Można je zastąpić pełnymi definicjami właściwości dla wcześniejszych wersji języka. Pula jest pokazana jako klasa statyczna, ponieważ nie jest wymagane wiele pul. Jednak równie dopuszczalne jest używanie klas instancji dla pul obiektów.

 



  

        

      
    
            
    

       przestrzeń nazw  DesignPattern.Objectpool  ;  // Klasa PooledObject jest typem, którego instancja jest kosztowna lub powolna,  // lub ma ograniczoną dostępność, dlatego należy ją przechowywać w puli obiektów.  klasa  publiczna  PooledObject  {  private  DateTime  _createdAt  =  DateTime  .  teraz  ;  public  DateTime  CreatedAt  {  get  {  return  _createdAt  ;  }  }  publiczny  ciąg  TempData     





   

           {  dostać  ;  zestaw  ;  }  }  // Klasa Pool kontroluje dostęp do obiektów znajdujących się w puli. Przechowuje listę dostępnych obiektów i   // kolekcję obiektów, które zostały pozyskane z puli i są w użyciu. Pula zapewnia, że ​​zwolnione obiekty   // wracają do odpowiedniego stanu, gotowego do ponownego użycia.  public  static  class  Pool  {  private  static  List  <  PooledObject  >  _available  =  new  List  < 
          

       
    
         
        
               0
            
                   0
                 Obiekt w puli  >();  private  static  Lista  <  PooledObject  >  _inUse  =  nowa  lista  <  PooledObject  >();  public  static  PooledObject  GetObject  ()  {  lock  (  _dostępny  )  {  if  (  _dostępny  .  Count  !=  )  {  PooledObject  po  =  _dostępny  [  ];  _w użyciu 
                0
                 
            
            
            
                    
                
                 
            
        
    

        
    
         .  Dodaj  (  po  );  _dostępny  .  Usuń w  (  );  zwrot  po  ;  }  else  {  Obiekt w puli  po  =  nowy  Obiekt w puli  ();  _w użyciu  .  Dodaj  (  po  );  zwrot  po  ;  }  }  }  public  static  void  ReleaseObject  (  PooledObject  po  )  {  CleanUp  ( 

         
        
            
            
        
    

        
    
          
    
 po  );  lock  (  _dostępny  )  {  _ dostępny  .  Dodaj  (  po  );  _w użyciu  .  Usuń  (  po  );  }  }  private  static  void  CleanUp  (  PopuledObject  po  )  {  po  .  TempData  =  null  ;  }  } 

W powyższym kodzie obiekt PooledObject ma właściwości na czas, w którym został utworzony, oraz inny, który może być modyfikowany przez klienta, tj. resetowany, gdy obiekt PooledObject jest ponownie umieszczany w puli. Pokazany jest proces czyszczenia, po zwolnieniu obiektu, upewniając się, że jest on w prawidłowym stanie, zanim będzie można ponownie zażądać go z puli.

Jawa

Java obsługuje łączenie wątków za pośrednictwem java.util.concurrent.ExecutorService i innych powiązanych klas. Usługa executora ma pewną liczbę „podstawowych” wątków, które nigdy nie są odrzucane. Jeśli wszystkie wątki są zajęte, usługa przydziela dozwoloną liczbę dodatkowych wątków, które są później odrzucane, jeśli nie są używane przez określony czas wygaśnięcia. Jeśli więcej wątków nie jest dozwolonych, zadania można umieścić w kolejce. Wreszcie, jeśli ta kolejka może być zbyt długa, można ją skonfigurować tak, aby zawieszała żądający wątek.

   
	  
	  
	  
	
	   
		 
	
	    
		  
	
	   klasa  publiczna  PooledObject  {  public  String  temp1  ;  publiczny  Ciąg  temp2  ;  publiczny  ciąg  temp3  ;  publiczny  String  getTemp1  ()  {  return  temp1  ;  }  public  void  setTemp1  (  String  temp1  )  {  to  .  temp1  =  temp1  ;  }  publiczny  ciąg  getTemp2  
		 
	
	    
		  
	
	   
		 
	
	    
		  
	 ()  {  zwróć  temp2  ;  }  public  void  setTemp2  (  String  temp2  )  {  to  .  temp2  =  temp2  ;  }  public  String  getTemp3  ()  {  return  temp3  ;  }  public  void  setTemp3  (  String  temp3  )  {  to  .  temp3  =  temp3  ;  } 
 } 
   
	     
	        
	      public  class  PooledObjectPool  {  private  static  long  expTime  =  6000  ;  //6 sekund  public  static  HashMap  <  PooledObject  ,  Long  >  available  =  new  HashMap  <  PooledObject  ,  Long  >  ();  public  static  HashMap  <  PooledObject  ,  Long  >  inUse  =    
	
	     
		   
		  
			     nowy  HashMap  <  PooledObject  ,  Long  >  ();  publiczne  zsynchronizowane  statyczne  PooledObject  getObject  ()  {  long  now  =  System  .  bieżący CzasMillis  ();  if  (  !  available  .  isEmpty  ())  {  for  (  Map  .  Entry  <  PooledObject  ,  Long  >  entry  :   
				       
					
				  
					    
					   dostępny  .  entrySet  ())  {  if  (  now  -  entry  .  getValue  ()  >  expTime  )  {  // obiekt wygasł  popElement  (  dostępny  );  }  else  {  PooledObject  po  =  popElement  (  dostępny  ,  wpis  .  getKey  ());  push  (  w użyciu  ,  po  ,  teraz  
					 
				
			
		

		
		 
		
	
	      
		    
		   );  powrót  po  ;  } }  }  //  albo żaden PooledObject nie jest dostępny, albo każdy wygasł, więc zwróć nowy  return  createPooledObject  (  now  );  }  private  synchronized  static  PooledObject  createPooledObject  (  od dawna  )  {  PooledObject  po  =  new  PooledObject  (  );  push  (  w użyciu  ,  po  ,  teraz  ); 
		 
    

	      
			    
		 
	

	      zwrot  po  ;  }  prywatny  zsynchronizowany  static  void  push  (  HashMap  <  PooledObject  ,  Long  >  map  ,  PooledObject  po  ,  long  now  )  {  map  .  umieścić  (  po  ,  teraz  );  }  public  static  void  releaseObject  (  PooledObject  po  )  { 
		
		 
		
	
	
	      
		   sprzątanie  (  po  );  dostępny  .  put  (  po  ,  System  .  currentTimeMillis  ());  w użyciu  .  usunąć  (  po  );  }  private  static  PooledObject  popElement  (  HashMap  <  PooledObject  ,  Long  >  map  )  {  Map  .  Wpis  <  obiekt w puli  ,  długi  >    
		   
		 
		 
		  
	
	
	     wpis  =  mapa  .  zestaw wpisów  ().  iterator  ().  następny  ();  Klucz  PooledObject  =  wpis  .  pobierz klucz  ();  //Długa wartość=entry.getValue();  mapa  .  usuń  (  wpis  .  getKey  ());  klawisz  powrotu  ;  }  private  static  PooledObject  popElement  (  HashMap  <  PooledObject  ,  Long  >     
		
		 
	
	
	     
		
		
		
	
 mapa  ,  klucz  PooledObject  )  {  mapa  .  usuń  (  klucz  );  klawisz  powrotu  ;  }  public  static  void  cleanUp  (  PopuledObject  po  )  {  po  .  setTemp1  (  null  );  po  .  setTemp2  (  null  );  po  .  setTemp3  (  null  );  }  } 

Zobacz też

Notatki

Linki zewnętrzne