IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

Object pool (fond objektov)

Návrhový vzor Object pool alebo Object fond (v anglickej literatúre sa objavuje pojem "pool", v článku budem používať pojem "fond") nám uchováva objekty na neskoršie použitie. To sa nám hodí v situáciách, kde je vytváranie inštancie príliš zložité alebo trvá príliš dlho.

Motivácia

Ak pracujeme na mnohých miestach sa triedou, ktorá je zložitá na vytváranie, jej opakovaná konštrukcia nám spomaľuje celý program. Návrhovým vzor object fond sa sám stará o vytváranie a odovzdávanie objektov zvyšku programu s tým, že už vytvorené objekty nemaže, ale uchováva na ďalšie použitie.

Príklad

Prejdem hneď k praktickej časti. Snáď každá webová aplikácia využíva databázu pre uchovávanie dát. S tým súvisí pripojenie k samotnej databáze, kedy si aplikácia musí vytvoriť spojenie. Takéto spojenie môže v extrémnych prípadoch trvať aj niekoľko sekúnd. Ak takých pripojenie vytvárame v aplikácii viac (napríklad kvôli viac vláknam, alebo dokonca pre každý dotaz), je odozva aplikácia vysoká.

Z tejto situácie nás zachráni fond. V našom prípade bude udržiavať pripojenie k databáze. Pokiaľ sa bude nejaká časť aplikácie chcieť spýtať na niečo databázy, požiada fond o pripojení. Akonáhle dotaz vykoná, vráti pripojenie späť fondu, ktorý ho "zrecykluje" a môže potom odovzdať ďalšej časti programu, ktorá o pripojenie požiada. Hlavný prínos teda spočíva v eliminácii opätovného vytvárania pripojenie, ktoré na celej operácii trvá najdlhšie.

Implementácia

Najprv sa pozrieme, ako by takýto fond z minulého príklad vyzeral, a potom si vysvetlíme jeho funkčnosť.

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ákladnej implementácii iba dve metódy. Jedna vracia pripojenie, pomocou druhej metódy pripojenia vraciame späť fondu. Až keď vrátime pripojenie späť fondu, môže ho fond odovzdať inej časti programu. Aby sme si situáciu demonštrovali, použijem nasledujúci program. Vytvorí sa 20 vlákien, ktoré sa uspí na náhodnú dobu. Po prebudení požiadajú fond o pripojení. Aby bolo lepšie vidieť, že sa vytvára menej pripojenia, než je vlákien, budem u každého pripojenie vypisovať informáciu o tom, že bolo práve vytvorené.

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

Ako je vidieť, hoci chcelo 20 vlákien pripojenie, v skutočnosti sa vytvorili pripojenie len 3. keby si každé vlákno vytvorilo vlastné pripojenie, zvýši sa potrebný čas, ktorý bude potrebný pre výpočet, ďalej bude viac zahltené linka aj samotná databázy, pretože bude musieť udržiavať niekoľko pripojení súčasne.

Možná konfigurácia

Niektoré objekty nám môžu zaberať veľa miesta. Ak použijeme je vo fonde, môže nám rýchlo dôjsť pamäť. V takýchto situáciách spravidla nechceme mať neobmedzený počet existujúcich inštancií. Môžeme dať fondu parameter, ktorý nám povie maximálny počet inštancií, ktoré má uchovávať. Pokiaľ by chcelo vlákno ďalšiu inštanciu, môže metóda ZiskejPripojeni napríklad vyhodiť výnimku, alebo vrátiť null. Druhý prístup spočíva v tom, že si zadefinujeme dve metódy: ZiskejPripoje­niIhned a ZiskejPripoje­niCekam. V prvom prípade by sa (ak nie sú žiadne voľné objekty) vytvorila nová inštancie. Pri vrátení objektov späť by sa objekt, ktorý by bol už nad hranicu, neuložil späť, ale rovno zmazal. Pri volaní funkcie ZiskejPripoje­niCekam by sa vlákno zablokovalo do doby, než bude voľná inštancie vo fonde. Ktorý spôsob použiť závisí od konkrétnej situácie. V našom prípade optimalizujeme predovšetkým medzi časom a pamäťou.

Hlavný problém, ktorý s Fondom súvisí, je, že musíme vracať objekty späť. Na prvý pohľad sa jedná o relatívne jednoduchý princíp, ale nie všetci programátori ho dodržiavajú. Prvým problém, ktorý môže nastať, je, že programátor bude objekt z fondu používať aj po jeho návrate späť. Táto situácia sa dá vyriešiť obalením návratového objektu - viď. článok o proxy. Proxy zabezpečí, aby objekt nešiel použiť po tom, čo sa navráti späť do fondu. Druhým prípadom je situácia, kedy programátor objekt vôbec vracať nebude. S takou situáciou sa vysporiadame len veľmi ťažko a tu záleží na konkrétnom jazyku. Opäť by sa uplatnil návrhový vzor proxy, ale napríklad v C ++ by sa vracala hodnota priamo (nie ukazovateľom). Tým by sa zabezpečilo, že sa objekt vráti späť do Fondu po opustení kontexte (napríklad v destruktor proxy). VC # by sme mohli napríklad definovať proxy odvodené od IDisposable, ktorý síce stále vyžaduje nejakú akciu od programátora, ale stále je to viac ako nič.

Viacvláknové aplikácie

Hoci bola prvá ukážka demonštrovaná pomocou viacerých vlákien, v reálnej aplikácii sa o vhodnú implementáciu nejde. Fond sa vôbec nestará o synchronizáciu, ako by to malo byť. Nižšie je ukážka fondu tak, ako by mal byť implementovaný. Viacvláknové mechanizmy sú mimo obsah tohto článku, preto pre plné pochopenie odporúčam prečítať seriál Paralelné programovanie a viacvláknové aplikácie v C# .NET. Pretože budem používať viac tried, a nechcem zahltiť celý článok kódom, kompletné riešenie bude v priloženom archíve pod článkom. Spojíme tu synchronizáciu, maximálny počet inštancií aj navrátenie späť do Fondu bez priameho volania metódy. Tu uvediem len UML diagram, aký je výsledok.

diagram - Návrhové vzory

Všimnime si, ako sa nám kód rozrástol. To je daň za to, keď chceme pracovať s viacerými vláknami súčasne. Tiež by som chcel upozorniť na metódu ZiskejPripoje­niPockam. Vlákno pravidelne uspávame do doby, než sa uvoľní objekt, ktorý potrebujeme. V rámci jednoduchosti je metóda definovaná týmto spôsobom, ale možno ďalej optimalizovať (napríklad pomocou semaforov), to je však mimo zameranie tohto článku.

Záver

Fond použijeme v programoch, kde chceme zamedziť opätovnému vytváraniu inštancie, alebo chceme mať kontrolu nad tým, koľko objektov existuje. Môžeme počet existujúcich objektov kontrolovať, a to aj keď pri písaní kódu nevieme, koľko objektov bude aplikácia reálne potrebovať.


 

Všetky články v sekcii
Návrhové vzory
Článok pre vás napísal Patrik Valkovič
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity