abclinuxu.cz AbcLinuxu.cz itbiz.cz ITBiz.cz HDmag.cz HDmag.cz abcprace.cz AbcPráce.cz
AbcLinuxu hledá autory!
Inzerujte na AbcPráce.cz od 950 Kč
Rozšířené hledání
×

včera 20:40 | Zajímavý článek

Lukáš Růžička v článku S Hydrogenem za lepší rytmus aneb bubeníkem snadno a rychle na MojeFedora.cz představuje automatického bubeníka s názvem Hydrogen (Wikipedie): Hydrogen je velmi vydařený program, který rozhodně nesmí chybět ve výbavě žádného linuxového muzikanta. Umožňuje nejen vytváření jednoduchých bicích doprovodů, ale také sofistikované programování bicích a perkusí, jehož výsledek se naprosto vyrovná drahým

… více »
Ladislav Hagara | Komentářů: 0
včera 13:55 | Zajímavý projekt

UPSat (Twitter) je první open source nanodružice (CubeSat). Jedná se o společný projekt nadace Libre Space Foundation a University of Patras. Repozitáře projektu jsou k dispozici na GitHubu. Pod Libre Space Foundation patří také projekt SatNOGS (zprávička), projekt globální sítě open source pozemních satelitních stanic, vítězný projekt soutěže The Hackaday Prize 2014. UPSat je součástí mise QB50 (Twitter). ID UPSatu je GR02. GPS přijímač na UPSatu je od české společnosti SkyFox Labs. Součástí mise QB50 je i česká nanodružice VZLUSAT-1 s ID CZ02.

Ladislav Hagara | Komentářů: 3
21.4. 15:00 | Komunita

V diskusním listu Thunderbird planning vývojáři poštovního klienta Thunderbird řeší, zda by nebylo možné budoucí Thunderbird postavit nad webovými technologiemi, tj. nad Electronem, stejně jako například Nylas Mail. Gecko, nad kterým je Thunderbird postaven, se má hodně změnit. V plánu je odstranění vlastností, které Firefox už nepotřebuje, ale Thunderbird je na nich závislý [Hacker News, reddit].

Ladislav Hagara | Komentářů: 83
21.4. 10:22 | Bezpečnostní upozornění

Společnost Oracle vydala čtvrtletní bezpečnostní aktualizaci svých softwarových produktů (CPU, Critical Patch Update). Opraveno bylo celkově 299 bezpečnostních chyb. V Oracle Java SE je například opraveno 8 bezpečnostních chyb. Vzdáleně zneužitelných bez autentizace je 7 z nich. V Oracle MySQL je opraveno 39 bezpečnostních chyb. Vzdáleně zneužitelných bez autentizace je 11 z nich.

Ladislav Hagara | Komentářů: 6
21.4. 10:00 | Pozvánky

V úterý 25. dubna proběhne další Prague Containers Meetup. Přijďte se nechat inspirovat jak zlepšit build/delivery pipeline vašich kontejnerových aplikací.

little-drunk-jesus | Komentářů: 2
20.4. 21:33 | Komunita

Na Launchpadu se objevilo kódové jméno následující verze Ubuntu. Ubuntu 17.10 bude Artful Aardvark (mazaný hrabáč) [OMG! Ubuntu!].

Ladislav Hagara | Komentářů: 10
20.4. 20:11 | Zajímavý software

MojeFedora.cz informuje, že společnost Nylas oznámila vydání verze 2.0 poštovního klienta Nylas Mail (původně Nylas N1), která již plně podporuje Linux. Obchodní model společnosti je tzv. open core. Samotný klient je open source, ale uživatel si musí připlatit za některé pokročilé funkce. V základu se lze připojit k GMailu nebo libovolnému účtu přes IMAP. Podpora Exchange je pouze v placené verzi. Klient je napsaný nad Electronem.

Ladislav Hagara | Komentářů: 12
20.4. 15:55 | Zajímavý článek

České centrum pro investigativní žurnalistiku (ČCIŽ) publikovalo na svých stránkách článek s názvem Je česká státní správa „rukojmím Microsoftu“?. Drtivá většina české veřejné správy je závislá na výrobcích softwarového gigantu Microsoft – a nijak zvlášť jí to nevadí.

Ladislav Hagara | Komentářů: 18
20.4. 02:48 | Nová verze

Google Chrome 58 byl prohlášen za stabilní. Nejnovější stabilní verze 58.0.3029.81 tohoto webového prohlížeče přináší řadu oprav a vylepšení (YouTube). Opraveno bylo 29 bezpečnostních chyb. Mezi nimi i chyba umožňující phishing s unicode doménami.

Ladislav Hagara | Komentářů: 0
19.4. 22:44 | Nová verze

Po šesti týdnech od vydání verze 52.0 byla vydána verze 53.0 webového prohlížeče Mozilla Firefox. Z novinek lze upozornit například na nové kompaktní vzhledy – tmavý z Firefoxu Developer Edition a jeho světlá varianta. Na Linuxu byla ukončena podpora procesorů starších než Pentium 4 a AMD Opteron. Podrobné informace v poznámkách k vydání a na stránce věnované vývojářům. Řešeny jsou také bezpečnostní chyby.

Ladislav Hagara | Komentářů: 11
Chystáte se pořídit CPU AMD Ryzen?
 (4%)
 (35%)
 (0%)
 (7%)
 (45%)
 (10%)
Celkem 273 hlasů
 Komentářů: 31, poslední 20.4. 21:26
    Rozcestník

    OctaSTD - bezpečný a rychlý multithreading a další změny

    23.3. 18:05 | Přečteno: 906× | octaforge | Výběrový blog

    Poslední zápisek byl celkem nedávno, ale od té doby se událo celkem dost změn, hlavně co se týče připravovaného paralelního frameworku, který umožní využití vícejádrových CPU bez obvyklých problémů spojených s vlákny a jejich synchronizací.

    Ale první na chvíli zpět ke korutinám.

    Změny v korutinách

    Díky tomu, že korutiny jsou víceméně lehké vlákna, ale s kooperativním schedulingem, je můžeme jednoduše využít jako základ pro lehké vlákna s vlastním schedulerem. Ale aby to opravdu fungovalo a bylo optimální, potřebujeme pár věcí.

    Stack alokátory

    Korutiny si alokují vlastní stacky. Jednou strategií je prostě dát korutině velikost stacku a potom udělat alokaci uvnitř. To ale není flexibilní, takže OctaSTD má podporu pro stack alokátory.

    Stack alokátor vypadá stukturálně nějak takto, podobně jako v Boost.Context:

    
    struct stack_allocator {
        stack_context allocate();
        void deallocate(stack_context &) noexcept;
    };
    

    Velikost stacku si určí alokátor, může to být třeba argument při konstrukci alokátoru. V OctaSTD jsou dva výchozí stack alokátory. Jeden je fixedsize_stack, ten druhý protected_fixedsize_stack. Jak jejich název napovídá, alokují stacky s fixní velikostí. Implementačně používají mmap() s MAP_ANON na POSIX systémech a VirtualAlloc na Windows. Jejich velikost je v OctaSTD vždy násobek velioksti jedné stránky (jedná stránka je na x86 4 KiB).

    Hranice velikosti a výchozí velikost jsou dány strukturou stack_traits. Ta poskytuje několik funkcí, které alokátoru řeknou, zda je velikost stacku nějak omezená systémem, jaké jsou limity a jaká je výchozí velikost. Výchozí velikost stacku je na běžných architekturách 64 KiB.

    Protected stack je v podstatě to samé; jediný rozdíl je, že jedna stránka na konci stacku je chráněná paměť, takže místo stack overflow dostaneme při přetečení předvídatelně segfault.

    Stack pool

    Pokud implementujeme lehké vlákna a scheduler, bude jich možná i hodně, a alokovat pro každou korutinu nový stack zvlášť není optimální. Proto má OctaSTD stack pool. Ten dělá dvě věci:

    Stack pool má metodu get_allocator(), která vrátí stack allocator alokující ze stack poolu.

    Rostoucí stacky

    Chtěl jsem implementovat i rostoucí stacky, tzn. takové, které budou postupně rezervovat více místa jak je potřeba. To se dá implementovat pomocí víceméně nezdokumentovaných API z libgcc pro GCC a Clang na POSIX systémech, a pak se musí kompilovat s -fsplit-stack. Bohužel toto není možné na Windows a na ostatních kompilátorech. Proto jsem se rozhodl rostoucí stacky neimplementovat - pokud je nemůžu garantovat na všech platformách, je lepší je nemít vůbec. Výchozí velikost pevného stacku by měla ale měla skoro vždy stačit.

    Generátory

    Standardní korutiny nejsou optimální jako generátory. Proto jsem přidal nový typ pro generátory. Rozdíly jsou v podstatě takové:

    S generátory se dá psát třeba takový kód:

    
    auto f = [](auto yield) {
        for (int i: range(5, 26, 5)) {
            yield(i);
        }
    };
    for (int i: generator<int>{f}) {
        writefln("generated: %s", i);
    }
    

    Channels

    OctaSTD teď implementuje tzv. channels, podobné jako třeba v jazyce Go. Pro ty, co je neznají, je to v podstatě thread-safe queue. V OctaSTD mají channels momentálně takový interface:

    
    template<typename T>
    struct channel {
        void put(T const &);
        void put(T &&);
        T get();
        bool try_get(T &);
        bool is_closed();
        void close();
    };
    

    Funguje to tak, že metoda .put(x) vloží do fronty hodnotu a probudí jedno vlákno, které na hodnotu čeká. Metoda .get() oproti tomu zkontroluje, jestli je ve frontě už nějaká hodnota; pokud ano, tak si ji to vezme, pokud ne, tak pozastaví vlákno a čeká na .put(x). Jeden channel může být sdílený mezi několika vlákny (třeba několik vláken může na něm volat .put(x) a jedno vlákno několikrát .get() nebo i obráceně). Komunikace může probíhat oběma směry, ale vzhledem k tomu, že jde o jednu frontu, tak většinou dává smysl jeden vybraný směr.

    Metoda .try_get(x) je rozdílná jen v tom, že na hodnotu nečeká a vrátí true v případě přítomnosti hodnoty ve frontě a false v případě její nepřítomnosti. A .is_closed() a .close() je celkem jasné. Po zavření kanálu jej není možné dále použít.

    Interní stav v channels používá reference counting. Díky tomu každé vlákno může mít vlastní referenci na channel a interní stav se neuvolní, dokud je alespoň jedno z vláken naživu. To dělá channels bezpečné, i s nepředvídatelným schedulerem.

    Concurrency

    Na základě korutin a kanálů implementuje OctaSTD svůj systém.

    Schedulery

    Základní strukturou je v tomto případě scheduler. To je struktura, která definuje chování úloh a s nimi spojených operací. Použití je u všech schedulerů stejné. Mají tento základní interface:

    
    struct scheduler {
        template<typename F, typename ...A>
        auto start(F &&func, A &&..args) -> std::result_of_t<F(A...)>;
        void spawn(std::function<void()>);
        void yield();
        generic_condvar make_condition();
    };
    

    První je metoda .start(f, a...). Ta v podstatě obvykle nahrazuje standardní main. Je první úlohou, kterou scheduler spustí (ta má také daleko větší stack, protože 64 KiB by prostě pro celý program nestačilo). Tato metoda vrací návratovou hodnotu z předané funkce, a neudělá to, dokud všechny úlohy neskončí.

    Následuje metoda .spawn(f). Ta spustí novou úlohu (vlákno). Co to vlastně ta úloha je si definuje sám scheduler.

    Dále je .yield(). Ta řekne scheduleru, že se má aktuálně běžící úloha pozastavit a přesunout na konec fronty. To způsobí spuštění další úlohy ve frontě s tím, že ta předchozí bude pokračovat později.

    A pak tu máme .make_condition(). Struktura generic_condvar je polymorfický wrapper pro buď normální std::condition_variable nebo vlastní interní strukturu, kterou si definuje sám scheduler. Poskytuje vlastně obecný interface k 1) pozastavení aktuální úlohy a čekání 2) probuzení jedné čekající úlohy 3) probuzení všech čekajících úloh. Jak to udělá, už je na implementaci, a záleží na tom, jak je definována úloha.

    Takže to funguje v podstatě tak, že "hlavní" stupní funkce se spustí přes .start(f, a...) a ta pak dále spouští další úlohy.

    Dostupné schedulery

    thread_scheduler

    Prvním dostupným schedulerem je thread_scheduler. Ten reprezentuje každou úlohu systémovým vláknem (std::thread) a samotný scheduling nechá na operačním systému. Jako generic_condvar používá std::condition_variable. Na určitých systémech (Windows) je tento přístup relativně pomalý a náročný. Na Linuxu a BSD jsou vlákna vcelku lehká, ale stále pomalejší než korutiny. Efektivně tento scheduler implementuje model 1:1 (kernel level scheduling).

    simple_coroutine_scheduler

    Další jednoduchý scheduler, který pro změnu implementuje model N:1 (user-level scheduling), kde každá úloha běží na hlavním systémovém vlákně a je reprezentována korutinou. Jako podmínková proměnná je použita jednoduchá vlastní implementace s jedním boolem a pár yieldy. Tento scheduler je velmi lehký, ale omezený na jedno vlákno.

    coroutine_scheduler

    Třetí, nejdůležitější scheduler implementuje hybridní M:N model, kde se spustí určitý počet systémových vláken (typicky stejný, jako počet fyzických vláken na CPU) a jednotlivé úlohy se reprezentují korutinami. Tento přístup kombinuje to nejlepší z těch dvou ostatních - máme velmi lehké úlohy, kterých můžeme tvořit stovky, a ty se rovnoměrně pospouští na jednotlivých systémových vláknech. Jako podmínková proměnná je použita trochu složitější vlastní implementace s čekací frontou.

    Dostupné API

    Pro pohodlí OctaSTD definuje několik API, které se nemusí explicitně volat na aktuálním scheduleru, protože znají aktuální scheduler interně.

    
    template<typename F, typename ...A>
    void spawn(F &&func, A &&...args);
    
    template<typename T>
    channel<T> make_channel();
    
    void yield();
    

    První API, spawn(f, ...) spustí na aktuálním scheduleru novou úlohu. Druhé API, make_channel<T> vytvoří komunikační kanál s typem T. A třetí API, yield() prostě zavolá stejné API na scheduleru.

    Použití

    Tady je ukázka z příkladů v OctaSTD repozitáři:

    
    #include <ostd/io.hh>
    #include <ostd/concurrency.hh>
    
    using namespace ostd;
    
    int main() {
        /* have an array, split it in two halves and sum each half in a separate
         * task, which may or may not run in parallel with the other one depending
         * on the scheduler currently in use - several schedulers are shown
         */
        auto foo = []() {
            auto arr = ostd::iter({ 150, 38, 76, 25, 67, 18, -15,  215, 25, -10 });
    
            auto c = make_channel<int>();
            auto f = [](auto c, auto half) {
                c.put(foldl(half, 0));
            };
            spawn(f, c, arr.slice(0, arr.size() / 2));
            spawn(f, c, arr + (arr.size() / 2));
    
            int a = c.get();
            int b = c.get();
            writefln("%s + %s = %s", a, b, a + b);
        };
    
        /* using thread_scheduler results in an OS thread spawned per task,
         * implementing a 1:1 (kernel-level) scheduling - very expensive on
         * Windows, less expensive on Unix-likes (but more than coroutines)
         */
        thread_scheduler{}.start([&foo]() {
            writeln("(1) 1:1 scheduler: starting...");
            foo();
            writeln("(1) 1:1 scheduler: finishing...");
        });
        writeln();
    
        /* using simple_coroutine_scheduler results in a coroutine spawned
         * per task, implementing N:1 (user-level) scheduling - very cheap
         * and portable everywhere but obviously limited to only one thread
         */
        simple_coroutine_scheduler{}.start([&foo]() {
            writeln("(2) N:1 scheduler: starting...");
            foo();
            writeln("(2) N:1 scheduler: finishing...");
        });
        writeln();
    
        /* using coroutine_scheduler results in a coroutine spawned per
         * task, but mapped onto a certain number of OS threads, implementing
         * a hybrid M:N approach - this benefits from multicore systems and
         * also is relatively cheap (you can create a big number of tasks)
         */
        coroutine_scheduler{}.start([&foo]() {
            writeln("(3) M:N scheduler: starting...");
            foo();
            writeln("(3) M:N scheduler: finishing...");
        });
    }
    
    /*
    (1) 1:1 scheduler: starting...
    356 + 233 = 589
    (1) 1:1 scheduler: finishing...
    
    (2) N:1 scheduler: starting...
    356 + 233 = 589
    (2) N:1 scheduler: finishing...
    
    (3) M:N scheduler: starting...
    356 + 233 = 589
    (3) M:N scheduler: finishing...
    */
    

    Závěr

    To je zatím všechno. Tyto API jsou všechny momentálně dost jednoduché a budou se rozšiřovat podle potřeby. To bude potom tématem dalšího zápisku.

           

    Hodnocení: 100 %

            špatnédobré        

    Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

    Komentáře

    Vložit další komentář

    28.3. 21:39 citanus | skóre: 8 | Cork (Ireland)
    Rozbalit Rozbalit vše Re: OctaSTD - bezpečný a rychlý multithreading a další změny

    pekna prace

    ISSN 1214-1267   www.czech-server.cz
    © 1999-2015 Nitemedia s. r. o. Všechna práva vyhrazena.