Object pool (fond objektů)

Návrhové vzory Object pool (fond objektů)

Návrhový vzor Object pool nebo Object fond (v anglické literatuře se objevuje pojem „pool“, v článku budu používat pojem „fond“) nám uchovává objekty na pozdější použití. To se nám hodí v situacích, kde je vytváření instance příliš složité nebo trvá příliš dlouho.

Motivace

Pracujeme-li na mnoha místech se třídou, která je složitá na vytváření, její opakovaná konstrukce nám zpomaluje celý program. Návrhovým vzor object fond se sám stará o vytváření a předávání objektů zbytku programu s tím, že již vytvořené objekty nemaže, ale uchovává na další použití.

Příklad

Přejdu hned k praktické části. Snad každá webová aplikace využívá databázi pro uchovávání dat. S tím souvisí připojení k samotné databázi, kdy si aplikace musí vytvořit spojení. Takové spojení může v extrémních případech trvat i několik sekund. Pokud takových připojení vytváříme v aplikaci více (například kvůli více vláknům, nebo dokonce pro každý dotaz), je odezva aplikace vysoká.

Z této situace nás zachrání fond. V našem případě bude udržovat připojení k databázi. Pokud se bude nějaká část aplikace chtít dotázat na něco databáze, požádá fond o připojení. Jakmile dotaz provede, vrátí připojení zpět fondu, který ho „zrecykluje“ a může poté předat další části programu, která o připojení požádá. Hlavní přínos tedy spočívá v eliminaci opětovného vytváření připojení, které na celé operaci trvá nejdéle.

Implementace

Nejprve se podíváme, jak by takový fond z minulého příklad vypadal, a poté si vysvětlíme jeho funkčnost.

class FondPripojeni
{
    private string JmenoServeru = "MujServer";
    private string Databaze = "Northwind";
    private List<SqlConnection> Connections = new List<SqlConnection>();

    public SqlConnection ZiskejPripojeni()
    {
        if (Connections.Count == 0)
            return new SqlConnection($"Server={$JmenoServer}; Database={$Database}; Trusted_Connection = true");
        else
        {
            SqlConnection Vratit = Connections[0];
            Connections.RemoveAt(0);
            return Vratit;
        }
    }

    public void VratitPripojeni(SqlConnection Pripojeni)
    {
        Connections.Add(Pripojeni);
    }
}

Fond má v základní implementaci pouze dvě metody. Jedna vrací připojení, pomocí druhé metody připojení vracíme zpět fondu. Teprve když vrátíme připojení zpět fondu, může jej fond předat jiné části programu. Abychom si situaci demonstrovali, použiji následující program. Vytvoří se 20 vláken, které se uspí na náhodnou dobu. Po probuzení požádají fond o připojení. Aby bylo lépe vidět, že se vytváří méně připojení, než je vláken, budu u každého připojení vypisovat informaci o tom, že bylo právě vytvořeno.

class Program
{
    static Random Ran = new Random();
    static FondPripojeni Fond = new FondPripojeni();

    static void Funkce()
    {
        int DelkaUspani = Ran.Next(0,3000);
        Thread.Sleep(DelkaUspani);
        Console.WriteLine("Žádost o objekt");
        SqlConnection Pripojeni=Fond.ZiskejPripojeni();
        // SIMULACE PRÁCE S OBJEKTEM
        Thread.Sleep(Ran.Next(0,200));
        Fond.VratitPripojeni(Pripojeni);
    }

    static void Main(string[] args)
    {
        for (int a = 0; a < 20; a++)
        {
            Thread vlakno = new Thread(Funkce);
            vlakno.Start();
        }
        Console.ReadKey();
    }
}

Výstup programu:

Žádost o objekt
Připojení bylo vytvořeno
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Připojení bylo vytvořeno
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Připojení bylo vytvořeno
Žádost o objekt
Žádost o objekt

Jak je vidět, ačkoliv chtělo 20 vláken připojení, ve skutečnosti se vytvořily připojení pouze 3. kdyby si každé vlákno vytvořilo vlastní připojení, zvýší se potřebný čas, který bude potřeba pro výpočet, dále bude více zahlcena linka i samotná databáze, protože bude muset udržovat několik spojení současně.

Možná konfigurace

Některé objekty nám mohou zabírat mnoho místa. Použijeme-li je ve fondu, může nám rychle dojít paměť. V takových situacích zpravidla nechceme mít neomezený počet existujících instancí. Můžeme dát fondu parametr, který nám řekne maximální počet instancí, které má uchovávat. Pokud by chtělo vlákno další instanci, může metoda ZiskejPripojeni například vyhodit výjimku, nebo vrátit null. Druhý přístup spočívá v tom, že si nadefinujeme dvě metody : ZiskejPripoje­niIhned a ZiskejPripoje­niCekam. V prvním případě by se (nejsou-li žádné volné objekty) vytvořila nová instance. Při navrácení objektů zpět by se objekt, který by byl již nad hranici, neuložil zpět, ale rovnou smazal. Při volání funkce ZiskejPripoje­niCekam by se vlákno zablokovalo do doby, než bude volná instance ve fondu. Který způsob použít závisí na konkrétní situaci. V našem případě optimalizujeme především mezi časem a pamětí.

Hlavní problém, který s Fondem souvisí, je, že musíme vracet objekty zpět. Na první pohled se jedná o relativně jednoduchý princip, ale ne všichni programátoři ho dodržují. Prvním problém, který může nastat, je, že programátor bude objekt z fondu používat i po jeho navrácení zpět. Tato situace lze vyřešit obalením návratového objektu – viz. článek o proxy. Proxy zajistí, aby objekt nešel použít po tom, co se navrátí zpět do fondu. Druhým případem je situace, kdy programátor objekt vůbec vracet nebude. S takovou situací se vypořádáme jen velmi těžko a zde záleží na konkrétním jazyce. Opět by se uplatnil návrhový vzor proxy, ale například v C++ by se vracela hodnota přímo (ne ukazatelem). Tím by bylo zajištěno, že se objekt vrátí zpět do Fondu po opuštění kontextu (například v destruktoru proxy). V C# bychom mohli například definovat proxy odvozené od IDisposable, který sice pořád vyžaduje nějakou akci od programátora, ale stále je to více než nic.

Vícevláknové aplikace

Ačkoliv byla první ukázka demonstrována pomocí více vláken, v reálné aplikaci se o vhodnou implementaci nejedná. Fond se vůbec nestará o synchronizaci, jak by tomu mělo být. Níže je ukázka fondu tak, jak by měl být implementován. Vícevláknové mechanismy jsou mimo obsah tohoto článku, proto pro plné pochopení doporučuji přečíst seriál Paralelní programování a vícevláknové aplikace v C#.NET. Protože budu používat více tříd, a nechci zahltit celý článek kódem, kompletní řešení bude v přiloženém archivu pod článkem. Spojíme zde synchronizaci, maximální počet instancí i navrácení zpět do Fondu bez přímého volání metody. Zde uvedu jen UML diagram, jaký je výsledek.

Diagram

Všimněme si, jak se nám kód rozrostl. To je daň za to, kdy chceme pracovat s více vlákny současně. Také bych chtěl upozornit na metodu ZiskejPripoje­niPockam. Vlákno pravidelně uspáváme do doby, než se uvolní objekt, který potřebujeme. V rámci jednoduchosti je metoda definována tímto způsobem, ale lze dále optimalizovat (třeba pomocí semaforů), to je ovšem mimo zaměření tohoto článku.

Závěr

Fond použijeme v programech, kde chceme zamezit opětovnému vytváření instance, nebo chceme mít kontrolu nad tím, kolik objektů existuje. Můžeme počet existujících objektů kontrolovat, a to i když při psaní kódu nevíme, kolik objektů bude aplikace reálně potřebovat.


 

Stáhnout

Staženo 169x (4.54 kB)

 

  Aktivity (3)

Článek pro vás napsal patrik.valkovic
Avatar
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu.

Jak se ti líbí článek?
Celkem (4 hlasů) :
4.254.254.254.25 4.25


 


Miniatura
Předchozí článek
Proxy (zástupce)
Miniatura
Všechny články v sekci
Návrhové vzory
Miniatura
Následující článek
Factory (tovární metoda)

 

 

Komentáře

Avatar
David Čápka
Tým ITnetwork
Avatar
David Čápka:

Tento vzor možná znáte v podobě ThreadPoolu z různých programovacích jazyků. ThreadPool umožňuje recyklovat vlákna, protože vytvoření nového vlákna jde přes OS a je to poměrně pomalé.

Odpovědět 19.11.2015 14:23
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Milan Křepelka
Redaktor
Avatar
Milan Křepelka:

Dobrý. Pěkně vysvětleno. Je vidět že sis s tím dal práci kterou si odvedl velmi dobře. Nicméně volba příkladu padla, myslím, lehce "naprázdno".

Problém s objektem SqlConnection není v jeho vytvoření, ale ve vytvoření fyzického spojení s SQL serverem přes síťouvou vrstvu atd..to je to co trvá dlouho. No a tato část už je by default poolovaná. Pro každé stejné credentials a základní parametry vytváří pooly ADO.NET ve své režii, takže už nebude nutné to poolovatn znovu a bude úměrné používat pouze using pattern.

Editováno 21.11.2015 12:40
 
Odpovědět  +1 21.11.2015 12:37
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 2 zpráv z 2.