Vzor fondu objektů - Object pool pattern

The vzor fondu objektů je software kreační návrhový vzor který používá sadu inicializovaných předměty stále připraven k použití - „bazén "- místo jejich přidělení a zničení na vyžádání. Klient fondu bude požadovat objekt z fondu a provádět operace s vráceným objektem. Když klient dokončí, vrátí objekt do fondu, nikoli zničit to; to lze provést ručně nebo automaticky.

Bazény objektů se primárně používají k výkonu: za určitých okolností fondy objektů výrazně zlepšují výkon. Fondy objektů se komplikují životnost objektu, protože objekty získané z fondu a vrácené do fondu nejsou v tuto chvíli skutečně vytvořeny nebo zničeny, a vyžadují proto při implementaci péči.

Popis

Když je nutné pracovat s velkým počtem objektů, jejichž vytvoření instance je zvlášť nákladné a každý objekt je potřeba pouze na krátkou dobu, může to mít nepříznivý vliv na výkon celé aplikace. V takových případech může být návrhový vzor fondu objektů považován za žádoucí.

Návrhový vzor fondu objektů vytváří sadu objektů, které mohou být znovu použity. Když je potřeba nový objekt, je požadován z fondu. Pokud je k dispozici dříve připravený objekt, je okamžitě vrácen, aby se zabránilo nákladům na vytvoření instance. Pokud ve fondu nejsou žádné objekty, vytvoří se a vrátí se nová položka. Když byl objekt použit a již není potřeba, je vrácen do fondu, což umožňuje jeho opětovné použití v budoucnu bez opakování výpočetně nákladného procesu vytváření instance. Je důležité si uvědomit, že jakmile byl objekt použit a vrácen, stávající odkazy se stanou neplatnými.

V některých fondech objektů jsou prostředky omezené, takže je zadán maximální počet objektů. Pokud je dosaženo tohoto počtu a je požadována nová položka, může být vyvolána výjimka nebo bude vlákno blokováno, dokud nebude objekt uvolněn zpět do fondu.

Návrhový vzor fondu objektů se používá na několika místech ve standardních třídách .NET Framework. Jedním z příkladů je .NET Framework Data Provider pro SQL Server. Protože vytváření připojení k databázi serveru SQL Server může být pomalé, je udržován fond připojení. Ukončení připojení ve skutečnosti nevzdá odkaz na SQL Server. Místo toho je připojení drženo ve fondu, ze kterého jej lze načíst při žádosti o nové připojení. To podstatně zvyšuje rychlost navazování spojení.

Výhody

Sdružování objektů může nabídnout významné zvýšení výkonu v situacích, kdy jsou náklady na inicializaci instance třídy vysoké a rychlost vytváření instancí a destrukce třídy je vysoká - v tomto případě lze objekty opakovaně použít a každé opětovné použití ušetří značné množství čas. Sdružování objektů vyžaduje prostředky - paměť a případně další zdroje, například síťové zásuvky, a proto je lepší, aby počet instancí používaných v jednom okamžiku byl nízký, ale není to nutné.

Sdružený objekt se získá v předvídatelném čase, kdy vytvoření nových objektů (zejména přes síť) může trvat proměnnou dobu. Tyto výhody platí většinou pro objekty, které jsou drahé z hlediska času, jako jsou připojení k databázi, připojení soketů, vlákna a velké grafické objekty, jako jsou písma nebo bitmapy.

V jiných situacích nemusí být jednoduché sdružování objektů (které neobsahují žádné externí prostředky, ale pouze zabírají paměť) efektivní a může snížit výkon.[1] V případě jednoduchého sdružování paměti se alokace desek technika správy paměti je vhodnější, protože jediným cílem je minimalizovat náklady na přidělení paměti a uvolnění paměti snížením fragmentace.

Implementace

Fondy objektů lze implementovat automatizovaným způsobem v jazycích, jako je C ++, prostřednictvím inteligentní ukazatele. V konstruktoru inteligentního ukazatele lze objekt požadovat z fondu a v destruktoru inteligentního ukazatele lze objekt uvolnit zpět do fondu. V jazycích shromažďovaných odpadky, kde neexistují žádné destruktory (u kterých je zaručeno, že budou volány jako součást odvíjení zásobníku), fondy objektů musí být implementovány ručně, výslovným vyžádáním objektu z továrna a vrácení objektu voláním metody dispose (jako v zlikvidovat vzor ). Používat finalizátor to není dobrý nápad, protože obvykle neexistují žádné záruky, kdy (nebo jestli) bude spuštěn finalizátor. Místo toho by mělo být použito „zkusit ... konečně“, aby bylo zajištěno, že získání a uvolnění objektu je výjimečně neutrální.

Manuální fondy objektů jsou snadno implementovatelné, ale těžší k použití, jak vyžadují manuální správa paměti objektů bazénu.

Manipulace s prázdnými bazény

Fondy objektů využívají jednu ze tří strategií ke zpracování požadavku, pokud ve fondu nejsou žádné náhradní objekty.

  1. Nepodařilo se poskytnout objekt (a vrátit chybu klientovi).
  2. Přiřaďte nový objekt, čímž zvětšíte velikost fondu. Bazény, které to dělají, vám obvykle umožňují nastavit značka vysoké vody (maximální počet použitých objektů).
  3. V vícevláknové prostředí, fond může blokovat klienta, dokud jiné vlákno nevrátí objekt do fondu.

Úskalí

Při psaní fondu objektů musí programátor dávat pozor, aby se ujistil, že stav objektů vrácených do fondu se resetuje zpět do rozumného stavu pro další použití objektu. Pokud to není dodrženo, objekt bude často v nějakém stavu, který byl neočekávaný klientským programem a může způsobit selhání klientského programu. Fond je zodpovědný za resetování objektů, nikoli klientů. Skupiny objektů plné objektů se nebezpečně zatuchlým stavem se někdy nazývají žumpy objektů a považují se za anti-vzor.

Přítomnost zatuchlého stavu není vždy problém; stává se nebezpečným, když přítomnost zatuchlého stavu způsobí, že se objekt chová odlišně. Například objekt, který představuje podrobnosti ověřování, se může rozbít, pokud není příznak „úspěšně ověřen“ resetován před jeho rozdáním, protože bude indikovat, že je uživatel správně ověřen (možná jako někdo jiný), když se ještě nepokusil ověřit. Bude však fungovat dobře, pokud se vám nepodaří resetovat určitou hodnotu, která se používá pouze k ladění, například identitu posledního použitého ověřovacího serveru.

Nedostatečné resetování objektů může také způsobit únik informací. Pokud objekt obsahuje důvěrná data (např. Čísla kreditních karet uživatele), která nejsou vymazána před předáním objektu novému klientovi, může škodlivý nebo bugující klient zveřejnit data neoprávněnému subjektu.

Pokud je fond používán více vlákny, může potřebovat prostředky, které zabrání paralelním vláknům v uchopení a pokusu o opětovné použití stejného objektu paralelně. To není nutné, pokud jsou sdružené objekty neměnné nebo jinak bezpečné pro vlákna.

Kritika

Některé publikace nedoporučují používat sdružování objektů v určitých jazycích, například Jáva, zejména pro objekty, které využívají pouze paměť a neobsahují žádné externí prostředky[který? ]. Oponenti obvykle říkají, že alokace objektů je v moderních jazycích relativně rychlá sběrači odpadků; zatímco operátor Nový potřebuje jen deset instrukcí, klasický Nový - vymazat pár nalezený v designech sdružování vyžaduje stovky z nich, protože dělá složitější práci. Většina sběratelů paměti také skenuje „živé“ odkazy na objekty, nikoli paměť, kterou tyto objekty používají pro svůj obsah. To znamená, že libovolný počet „mrtvých“ objektů bez odkazů lze za nízkou cenu zahodit. Naproti tomu udržování velkého počtu „živých“, ale nepoužívaných objektů zvyšuje dobu sběru odpadu.[1]

Příklady

Jít

Následující kód Go inicializuje fond prostředků zadané velikosti (souběžná inicializace), aby se předešlo problémům se zdroji prostřednictvím kanálů, a v případě prázdného fondu nastaví zpracování časového limitu, aby klientům zabránil čekat příliš dlouho.

// balíček balíčkůbalík bazénimport (	"chyby"	"log"	„math / rand“	„synchronizovat“	"čas")konst getResMaxTime = 3 * čas.Druhývar (	ErrPoolNotExist  = chyby.Nový("fond neexistuje")	ErrGetResTimeout = chyby.Nový("dostat časový limit zdroje"))// Zdrojtyp Zdroj struktur {	resId int}// NewResource Simuluje pomalé vytváření inicializace prostředků// (např. připojení TCP, získání symetrického klíče SSL, ověřování ověřování jsou časově náročné)func NewResource(id int) *Zdroj {	čas.Spát(500 * čas.Milisekunda)	vrátit se &Zdroj{resId: id}}// Zdroje simulace jsou časově náročné a náhodná spotřeba je 0 ~ 400msfunc (r *Zdroj) Dělat(workId int) {	čas.Spát(čas.Doba trvání(rand.Intn(5)) * 100 * čas.Milisekunda)	log.Printf("pomocí zdroje #% d dokončena práce% d dokončení  n", r.resId, workId)}// Pool založený na implementaci Go kanálu, aby nedocházelo k problémům se stavem závodutyp Bazén chan *Zdroj// Nový fond zdrojů zadané velikosti// Prostředky se vytvářejí souběžně, aby se ušetřil čas inicializace prostředkůfunc Nový(velikost int) Bazén {	p := udělat(Bazén, velikost)	wg := Nový(synchronizace.WaitGroup)	wg.Přidat(velikost)	pro i := 0; i < velikost; i++ {		jít func(resId int) {			p <- NewResource(resId)			wg.Hotovo()		}(i)	}	wg.Počkejte()	vrátit se p}// GetResource na základě kanálu, je zabráněno stavu závodu prostředku a je nastaven časový limit pro získání zdroje pro prázdný fondfunc (p Bazén) GetResource() (r *Zdroj, chybovat chyba) {	vybrat {	případ r := <-p:		vrátit se r, nula	případ <-čas.Po(getResMaxTime):		vrátit se nula, ErrGetResTimeout	}}// GiveBackResource vrací prostředky do fondu prostředkůfunc (p Bazén) GiveBackResource(r *Zdroj) chyba {	-li p == nula {		vrátit se ErrPoolNotExist	}	p <- r	vrátit se nula}// hlavní balíčekbalík hlavníimport (	„github.com/tkstorm/go-design/creational/object-pool/pool“	"log"	„synchronizovat“)func hlavní() {	// Inicializace skupiny pěti zdrojů,	// který lze upravit na 1 nebo 10, aby se zobrazil rozdíl	velikost := 5	p := bazén.Nový(velikost)	// Vyvolá prostředek k provedení úlohy ID	doWork := func(workId int, wg *synchronizace.WaitGroup) {		odložit wg.Hotovo()		// Získejte prostředek z fondu zdrojů		res, chybovat := p.GetResource()		-li chybovat != nula {			log.Println(chybovat)			vrátit se		}		// Zdroje, které se mají vrátit		odložit p.GiveBackResource(res)		// Využívejte prostředky k řešení práce		res.Dělat(workId)	}	// Simulujte 100 souběžných procesů a získejte zdroje z fondu aktiv	počet := 100	wg := Nový(synchronizace.WaitGroup)	wg.Přidat(počet)	pro i := 0; i < počet; i++ {		jít doWork(i, wg)	}	wg.Počkejte()}

C#

V .NET Knihovna základní třídy existuje několik objektů, které tento vzor implementují. System.Threading.ThreadPool je nakonfigurován tak, aby měl předdefinovaný počet vláken k přidělení. Když jsou vlákna vrácena, jsou k dispozici pro další výpočet. Lze tedy použít vlákna bez placení nákladů na vytvoření a likvidaci vláken.

Následuje ukázka základního kódu návrhového vzoru fondu objektů implementovaného pomocí C #. Pro stručnost jsou vlastnosti tříd deklarovány pomocí automaticky implementované syntaxe vlastnosti C # 3.0. Ty by mohly být nahrazeny úplnými definicemi vlastností pro dřívější verze jazyka. Pool je zobrazen jako statická třída, protože je neobvyklé, že je vyžadováno více fondů. Je však stejně přijatelné použít třídy instancí pro fondy objektů.

jmenný prostor DesignPattern.Objectpool {    // Třída PooledObject je typ, jehož vytvoření instance je nákladné nebo pomalé,    // nebo která má omezenou dostupnost, takže se má držet ve fondu objektů.    veřejnost třída PooledObject    {        soukromé Čas schůzky _createdAt = Čas schůzky.Nyní;         veřejnost Čas schůzky Vytvořeno        {            dostat { vrátit se _createdAt; }        }         veřejnost tětiva TempData { dostat; soubor; }    }    // Třída Pool je nejdůležitější třídou v návrhovém vzoru fondu objektů. Řídí přístup k    // sdružené objekty, udržování seznamu dostupných objektů a kolekce objektů, které již byly    // požadováno z fondu a stále se používá. Fond také zajišťuje, že objekty, které byly uvolněny    // jsou vráceny do vhodného stavu, připraveny na další vyžádání.     veřejnost statický třída Bazén    {        soukromé statický Seznam<PooledObject> _dostupný = Nový Seznam<PooledObject>();        soukromé statický Seznam<PooledObject> _při použití = Nový Seznam<PooledObject>();         veřejnost statický PooledObject GetObject()        {            zámek(_dostupný)            {                -li (_dostupný.Počet != 0)                {                    PooledObject po = _dostupný[0];                    _při použití.Přidat(po);                    _dostupný.RemoveAt(0);                    vrátit se po;                }                jiný                {                    PooledObject po = Nový PooledObject();                    _při použití.Přidat(po);                    vrátit se po;                }            }        }         veřejnost statický prázdnota ReleaseObject(PooledObject po)        {            CleanUp(po);             zámek (_dostupný)            {                _dostupný.Přidat(po);                _při použití.Odstranit(po);            }        }         soukromé statický prázdnota CleanUp(PooledObject po)        {            po.TempData = nula;        }    }}

V kódu výše PooledObject obsahuje dvě vlastnosti. Jedno zadržení času, kdy byl objekt poprvé vytvořen. Druhý obsahuje řetězec, který lze upravit pomocí clientbut, který se resetuje, když je PooledObject uvolněn zpět do fondu. To ukazuje proces vyčištění při uvolnění objektu, který zajišťuje, že je v platném stavu, než jej lze znovu vyžádat z fondu.

Jáva

Java podporuje sdružování vláken přes java.util.concurrent.ExecutorService a další související třídy. Služba exekutora má určitý počet „základních“ vláken, která nejsou nikdy zahozena. Pokud jsou všechna vlákna zaneprázdněna, služba přidělí povolený počet dalších vláken, která se později zahodí, pokud se po určitou dobu platnosti nepoužijí. Pokud nejsou povolena žádná další vlákna, lze úkoly umístit do fronty. Nakonec, pokud se tato fronta může příliš prodloužit, lze ji nakonfigurovat tak, aby pozastavila žádající vlákno.

veřejnost třída PooledObject {	veřejnost Tětiva teplota1;	veřejnost Tětiva temp2;	veřejnost Tětiva temp3;		veřejnost Tětiva getTemp1() {		vrátit se teplota1;	}	veřejnost prázdnota setTemp1(Tětiva teplota1) {		tento.teplota1 = teplota1;	}	veřejnost Tětiva getTemp2() {		vrátit se temp2;	}	veřejnost prázdnota setTemp2(Tětiva temp2) {		tento.temp2 = temp2;	}	veřejnost Tětiva getTemp3() {		vrátit se temp3;	}	veřejnost prázdnota setTemp3(Tětiva temp3) {		tento.temp3 = temp3;	}}
veřejnost třída PooledObjectPool {	soukromé statický dlouho expTime = 6000;// 6 sekund	veřejnost statický HashMap<PooledObject, Dlouho> dostupný = Nový HashMap<PooledObject, Dlouho>();	veřejnost statický HashMap<PooledObject, Dlouho> při použití = Nový HashMap<PooledObject, Dlouho>();		veřejnost synchronizované statický PooledObject getObject() {		dlouho Nyní = Systém.currentTimeMillis();		-li (!dostupný.je prázdný()) {			pro (Mapa.Vstup<PooledObject, Dlouho> vstup : dostupný.entrySet()) {				-li (Nyní - vstup.getValue() > expTime) { // objektu vypršela platnost					popElement(dostupný);				} jiný {					PooledObject po = popElement(dostupný, vstup.getKey());					tlačit(při použití, po, Nyní); 					vrátit se po;				}			}		}		// buď není k dispozici žádný PooledObject, nebo každému z nich vypršela platnost, proto vraťte nový		vrátit se createPooledObject(Nyní);	}			soukromé synchronizované statický PooledObject createPooledObject(dlouho Nyní) {		PooledObject po = Nový PooledObject();		tlačit(při použití, po, Nyní);		vrátit se po;        }	soukromé synchronizované statický prázdnota tlačit(HashMap<PooledObject, Dlouho> mapa,			PooledObject po, dlouho Nyní) {		mapa.dát(po, Nyní);	}	veřejnost statický prázdnota releaseObject(PooledObject po) {		cleanUp(po);		dostupný.dát(po, Systém.currentTimeMillis());		při použití.odstranit(po);	}		soukromé statický PooledObject popElement(HashMap<PooledObject, Dlouho> mapa) {		 Mapa.Vstup<PooledObject, Dlouho> vstup = mapa.entrySet().iterátor().další();		 PooledObject klíč= vstup.getKey();		 // Dlouhá hodnota = entry.getValue ();		 mapa.odstranit(vstup.getKey());		 vrátit se klíč;	}		soukromé statický PooledObject popElement(HashMap<PooledObject, Dlouho> mapa, PooledObject klíč) {		mapa.odstranit(klíč);		vrátit se klíč;	}		veřejnost statický prázdnota cleanUp(PooledObject po) {		po.setTemp1(nula);		po.setTemp2(nula);		po.setTemp3(nula);	}}

Viz také

Poznámky

  1. ^ A b Goetz, Brian (2005-09-27). „Java theory and practice: Urban performance legends, revisited“. IBM developerWorks. Archivovány od originál dne 2005-09-27. Citováno 2012-08-28.

Reference

externí odkazy