Paralelní programování v Qt IV - Blokující a neblokující paralelní výpočty

17.1.2017

foto Petr Bravenec

Petr Bravenec
+420 777 566 384
petr.bravenec@hobrasoft.cz

V minulých článcích jsme si ukázali nejjednodušší použití prostředků pro paralelní výpočty v knihovně Qt. Společným rysem všech příkladů uvedených v předchozích článcích bylo použití blokujících operací.

Různé názvy stejných funkcí

Podívejte se na některé funkce, které knihovna Qt pro paralelní výpočty nabízí (QtConcurrent Namespace):

Jeden paralelní algoritmus je v knihovně Qt dostupný hned pod čtymi názvy. Proč?

Funkce v QtConcurrent existují ve čtyřech variantách: s předponou blocking- nebo s příponou -ed.

Funkce nekončící na -ed (map, blockingMap) pracují na místě. Paralelní výpočet upravuje přímo vstupní množinu dat. Funkce končící na -ed (mapped, blockingMapped) vracejí upravená data. Původní množina dat zůstává beze změny.

Blokující operace

Funkce začínající na blocking- pracují, dokud není výpočet hotový. Jakmile takovou funkci zavoláte, program pokračuje na další řádek až po kompletním dokončení celého paralelního výpočtu.

Stejná pravidla pro pojmenování platí i pro všechny ostatní paralelní algoritmy. Stejné jmenné konvence jsou patrné i u ostatních funkcí a tříd knihovny Qt (například QString::trimmed() a další).

Blokující paralelní výpočty se dají snadno využít pro dávkové zpracování na povelové řádce, kde obvykle očekáváme, že dokončením výpočtu se ukončí i běh celého programu, a příliš nás nezajímá průběh výpočtu.

Neblokující operace

Zcela nepřijatelné jsou blokující operace u GUI aplikací. Tam uživatel oprávněně očekává, že výpočet bude moci přerušit, pozastavit či jej znovu spustit. Případně může uživatele jen docela obyčejně zajímat, jak dlouho bude výpočet ještě probíhat.

Voláním neblokující funkce se pouze nastartuje paralelní výpočet a program ihned pokračuje zpracováním dalšího řádku vašeho kódu. Bezprostředně po návratu z funkce (například mapped) není zatím provedený ani kousek paralelního výpočtu. Ten se bude provádět na pozadí nezávisle na ostatní činnosti programu. V GUI aplikaci tak můžete během paralelního výpočtu reagovat na tlačítka nebo můžete zobrazovat progressbar s průběhem výpočtu. Uživatel tak není odkázaný na vaši milost či nemilost a může aplikaci ovládat i v době, kdy probíhá paralelní výpočet.

Není-li volání funkce blokované a funkce samotná pouze nastartuje výpočet, musíme se o dokončení výpočtu dozvědět jiným způsobem, než je prostý návrat z blokující funkce. K tomu slouží třída QFuture.

QFuture

Připomeňme si, jak se knihovna Qt pro paralelní výpočty v nejjednodušším případě používá: připraví se vstupní množina dat v některém kontejneru, například QStringList, a tato data se předají některé paralelní funkci, například map():

QStringList data = readData();
QtConcurrent::blockingMap(data, mapFunkce);

V kontejneru data jsou po provedení výpočtu výsledné hodnoty. U funkce map() bez -ed na konci je situace jednoduchá - funkce map() funguje přímo na místě. Pokud chcete data zachovat netknutá, je potřeba výsledná data ukládat jinam. Takovým kontejnerem je u paralelních výpočtů třída QFuture.

QFuture není pouze kontejner. Poskytuje některé další metody pro jednoduché řízení výpočtu. Přes třídu QFuture lze výpočet pozastavit a znovu spustit, zrušit a zkontrolovat stav výpočtu (běžící, pozastavený, zrušený, dokončený). Pro jednoduchou synchronizaci poskytuje třída metodu waitForFinished().

QStringList data = readData();
QFuture<QString> future = QtConcurrent::map(data, mapFunkce);
future.waitForFinished();
QFutureIterator<QString> iterator(future);
while (iterator.hasNext()) {
    qDebug << iterator.next();
    }

Metoda waitForFinished() zablokuje program až do provedení celého paralelního výpočtu, metoda tedy poskytuje podobnou funkčnost jako blokované paralelní funkce. U interaktivních GUI je použití blokovaných operací nežádoucí. Jak využít třídy QFuture a QFutureWatcher si ukážeme v dalším pokračování tohoto seriálu.

Kompletní příklad: Přečte data ze standardního vstupu a vytiskne všechny řádky s čiselnými hodnotami:

#include 
#include 
#include 
#include 
#include 

bool isNumber(const QString& text) {
    bool ok;
    text.toInt(&ok);
    return ok;
}

int main(int, char **) {
    QTextStream stream(stdin);
    QList<QString> inputData;
    QString line;
    do {
        line = stream.readLine();
        inputData << line;
        } while (!line.isNull());

    QFuture<QString> future = QtConcurrent::filtered (
            inputData,
            isNumber
            );

    future.waitForFinished();
    QFutureIterator<QString> iterator(future);
    while (iterator.hasNext()) {
        QString x = iterator.next();
        qDebug() << x;
        }
}

Odkazy

Závěr

Tento článek je jedním z několika článků o paralelním programování v Qt. Sledujte tyto stránky, sledujte náš Twitter. Další díly budou následovat.

Hobrasoft s.r.o. | kontakt