Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

2. diel - Prvý viacvláknové aplikácie v C ++

V predchádzajúcej lekcii, Úvod do viacvláknových aplikácií v C a C ++ , sme si povedali základné pojmy k vícevláknovým aplikáciám v C ++, aké knižnice budeme používať, kedy viacvláknové aplikácie využijeme a ďalšie úvodné informácie. Dnes si konečne vytvoríme projekt, ako vo Visual Studiu, tak na Linuxe za použitia GCC kompileru. Aplikácia po spustení naštartuje ďalší vlákna, ktoré budú vypisovať nejaký text. Na tomto príklade si zároveň ukážeme, prečo potrebujeme vlákna synchronizovať a aké sú dôsledky zle synchronizovaných vlákien.

Základný program

Ako bolo povedané v minulej lekcii, budeme pracovať s knižnicou thread, ktorá je určená pre C ++ od štandardu C ++ 11. Táto knižnica obsahuje triedu std::thread, ktorá reprezentuje vlákno. Tento objekt prijíma ako parameter funkciu, ktorú má spustiť v novom vlákne. Do istej miery je nami odovzdaná funkcia ekvivalent funkcie main() hlavného programu. Je to teda funkcie, ktorá sa začne vykonávať ako prvý na novom vlákne. Vo chvíli, keď funkcia skončí (tj. Funkcie narazí na príkaz return alebo nie je zachytená výnimka), celé vlákno sa ukončí.

Nebudeme to dlho zdržovať a rovno si ukážeme náš prvý viacvláknové program:

#include <iostream>
#include <thread>

using namespace std;

void vypis0()
{
    while(true)
        cout.put('0');
}

void vypis1()
{
    while(true)
        cout.put('1');
}

int main()
{
    thread t0(vypis0);
    vypis1();
    return 0;
}

Poďme si program rozobrať. Najprv je importovaná knižnica iostream (pre štandardný vstup a výstup) spoločne s knižnicou thread (pre použitie vlákien). Rovnako ako všetky ostatné triedy v štandardnej knižnici, je aj trieda thread umiestnená v mennom priestore std.

Ďalej sú nadefinované dve funkcie - jedna vypisuje nuly a druhá jednotky. Všimnite si, že je použitá nekonečná slučka, funkcia teda nikdy neskončí a program budeme musieť ukončiť "násilnou" cestou.

Nakoniec vo funkcii main() vytvoríme nové vlákno, ktorému odovzdáme funkciu vypis0(). V tento okamih sa vlákno už môže spustiť (prečo môže si povieme za chvíľu). Následne zavoláme funkciu vypis1(). To znamená, že budeme mať dve vlákna - hlavné (to, ktoré patria k funkcii main() a vypisuje '1') a nami vytvorené (ktoré vypisuje '0').

Výstup z programu môže vyzerať napríklad nasledovne (u vás celkom iste vyzerať inak).

Konzolová aplikácia
00110111010111001111010111111111010111111111010000010111111111101111111111010111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000010111111111111111011111111110000000000000000000000000000000010111111111000000000000000000000000000000101111111110010111111111000000000000101111111111010111111111101011111111100000000000000101111111111000101111111110111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101111111111011111111101011111111111000000000000000000000000000000000000000000000000000000000000010111111111101111111110100000000000000000010111111111010111111111101011111111010111111111101111111100000000000101111111110111111111000000000000000000000000000000000000000000000001011111111110111111111000000000000001011111111010111111111101111111110101111111110000000000000000101111111111010111111111000000000000000000000000000000000000000000000000001111111111111111100000000000000000000000000000

Všimnite si, že jednotky a nuly nie sú úplne na striedačku, ako by sme mohli očakávať. Zo začiatku je vidieť, ako bežia obe vlákna súčasne a preto sa jednotky a nuly striedajú relatívne často. Naopak ku koncu výpisu možno vidieť dlhé sekvencie jedničiek alebo núl, to by nám mohlo napovedať, že jedno z vlákien bolo uspanie a tak do konzoly vypisuje iba druhé. Pre presnejšie meranie, ktoré vlákno beží, by bolo lepšie použiť špecializované nástroje. Problém s výpisom, ako ho máme vyššie, je, že výstup môže byť cachovanie (tj. Uložený do medzipamäte a potom vypísaný všetok naraz) a v takom prípade nemôžeme na základe výstupe súdiť nič.

Kompilácie

Určite sa už nemôžete dočkať, až si príklad vyskúšate sami. Preto sa teraz pozrieme na kompilácii nášho programu.

Kompilácie vo Visual Studio

Vo Visual Studiu je kompilácia úplne jednoduchá. Prvý krok je vytvoriť konzolovú aplikáciu štandardným spôsobom, viď. prvý konzolová aplikácie). Na rozdiel od GCC je knižnica pre prácu s vláknami automaticky importovaná do projektu. Jednoducho skopírujte kód vyššie a všetko by malo fungovať.

Problém môže nastať, ak používate predkompilované hlavičky. V takom prípade je vypnite (kliknite pravým tlačidlom na projekt -> Properties -> Configuration Properties -> C / C ++ -> Precompiled Headers -> Not Using Precompiled Headers) alebo na začiatok súboru dopíšte #include "pch.h" pre obsiahnutie předkompilovaných hlavičiek .

Kompilácie pre Linux a MacOS

Ako som už spomenul, pre kompiláciu na platformách Linux a MacOS budeme používať kompiler GCC. Ten by ste už mali mať nainštalovaný z predchádzajúcich kurzov. Budem predpokladať, že ste si program vyššie skopírovali a uložili do súboru vytvoreni.cpp. Potom bude príkaz pre kompiláciu nasledujúce:

g++ -std=c++11 -pthread -o program.exe vytvoreni.cpp

Pretože je knižnica thread dostupná až od štandardu C ++ 11, musíme špecifikovať minimálne tento štandard (tj. Prepínač -std). Ďalej je knižnica thread (pre platformu Linux a MacOS) postavená nad knižnicou pthread (čiže POSIX threads), ktorá je de facto štandard pre UNIX-like systémy. Prepínačom -pthread túto knižnicu zahrnieme do cesty, aby ju linker mohol nájsť a my mohli jej funkcie použiť (viac sa dozviete v článku o kompiláciu).

Tým máme kompiláciu programu prebranú. Sami si môžete skúsiť, ako sa program bude správať na vašom stroji s iným hardvérom. V ďalších príkladoch už nebudem príkazy uvádzať, pretože sú takmer totožné.

Konštruktor vlákna

Nakoniec by som sa ešte chcel vrátiť ku konstruktoru vlákna. Zo začiatku dnešnej lekcie sme si povedali, že sa po volaní konstruktoru vlákno môže spustiť (z toho logicky vyplýva, že nemusí). Čím je to teda vlastne dané? Ako inak, než operačným systémom. Ten obsahuje tzv. Scheduler (slovensky plánovač), ktorý určuje ako dlho, na ktorom jadre a ktoré vlákno pobeží. Pri volaní konstruktoru povieme operačnému systému, že máme nové vlákno, ktoré by sme chceli spustiť. O tom, kedy toto vlákno pobeží, si už rozhoduje operačný systém sám.

To je tiež hlavný dôvod, prečo je viacvláknové programovanie tak zložité. My programátori sme zvyknutí čítať program zhora nadol, tak, ako sú príkazy vykonávané. To je prirodzený postup, programujeme ak pre jedno vlákno. Avšak vo chvíli, kedy máme vlákien viac, nemáme kontrolu nad tým, čo sa kedy vykonáva. V takom prípade musíme počítať so všetkými možnými kombináciami (tj. Každý riadok z prvej funkcie sa môže vykonávať s ľubovoľným riadkom druhej funkcie). Jednoduchou matematikou zistíme, že počet situácií rastie exponenciálne a premyslieť si ich všetky je ťažké. Ako donútiť vlákna, aby medzi sebou mala aspoň nejakú synchronizáciu, si povieme v ďalších lekciách.

V budúcej lekcii, Čakanie na vlákno v C ++ a odovzdávanie parametrov , sa pozrieme na ďalšie operácie s vláknami, ako je spojenie a odovzdávanie parametrov, takže sa určite máte na čo tešiť!:)


 

Predchádzajúci článok
Úvod do viacvláknových aplikácií v C a C ++
Všetky články v sekcii
Paralelné programovanie a viacvláknové aplikácie v C ++
Preskočiť článok
(neodporúčame)
Čakanie na vlákno v C ++ a odovzdávanie parametrov
Č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