Podręcznik

2. Współbieżność

2.1. Przekazywanie argumentów i odbieranie wyników

W powyższym przykładzie - parametry do zadania były przekazywane przez wartość - i jest to zalecana metoda. Podobnie w zasadzie był odebrany wynik - przekazaliśmy przez wartość adres w pamięci gdzie należy go umieścić.

W przypadku dużych zbiorów danych przekazywanie przez wartość nie zawsze jednak będzie optymalnym rozwiązaniem. W tym przykladzie z sumowaniem - w klasycznym (nie równoległym) kodzie staralibyśmy się przekazać tablicę do zsumowania przez referencję, by uniknąć jej niepotrzebnego kopiowania. Jeśli jednak zmienimy deklarację funkcji do postaci:


void sortFunction(std::deque<double>& array, double *result)

kod przestanie się kompilować. W przypadku nie stałych referencji C++ wymaga jawnego wywołania std::ref na liście parametrów konstruktora std::thread.

W przypadku odbierania wyniku jest podobnie - jeśli typ argumentu result miałby być referecją do double a nie wskaźnikiem - to powinniśmy przekazać go poprzez std::ref()

Czasem także wynik jest odbierany bezpośrednio w argumencie przekazującym dane wejściowe - jeśli np. naszym zadaniem jest sortowanie wektora. Popatrzmy więc jak nasz przykład mógłby wyglądać jeśli zmienimy sumowanie na sortowanie:


#include "iostream"
#include "sstream"
#include "deque"
#include "random"
#include "algorithm"
#include "thread"

/** Metoda wypełnia tablicę przekazaną jej jako parametr liczbami losowymi.
 * Tablica jest wypełniana liczbami losowymi z zakresu 1..100 */
void fillRandmCollection(std::deque<std::deque<double>>& array) {
    std::random_device random_device;
    std::mt19937 random_engine(random_device());
    std::uniform_int_distribution<int> distribution_1_100(1, 100);

    for (auto& row : array) {
        for (auto& elem : row) {
            elem = distribution_1_100(random_engine);
        }
    }
}

/** Metoda sortuje pojedynczy wiersz */
void sortFunction(std::deque<double>& array) {
    std::sort(array.begin(), array.end(), [](double a, double b) {
        // zasypiamy na chwilę - by sztucznie wydłużyć jej działanie
        std::this_thread::sleep_for(std::chrono::nanoseconds (50));
        return a > b;
    });
}

int main() {
    // tablica do posortowania
    std::deque<std::deque<double>> toBeSorted(10, std::deque<double>(1000, 0) );

    // losowanie tablicy
    fillRandmCollection(toBeSorted);

    // sortowanie  w jednym wątku
    std::cout << "Sortuję w jednym wątku ... " << std::flush;
    auto tstart = std::chrono::high_resolution_clock::now();
    for (int i=0; i<toBeSorted.size(); ++i) {
        sortFunction(toBeSorted[i]);
    }
    auto tstop = std::chrono::high_resolution_clock::now();
    std::cout << " koniec, trwało " << std::chrono::duration_cast<std::chrono::milliseconds>(tstop-tstart).count()/1000.0 << " s\n";

    // sortowanie w wielu wątkach
    fillRandmCollection(toBeSorted);
    std::deque<std::thread> threads;
    std::cout << "Sortuję w wielu wątkach ... " << std::flush;
    tstart = std::chrono::high_resolution_clock::now();
    for (int i=0; i<toBeSorted.size(); ++i) {
        threads.emplace_back(sortFunction, std::ref(toBeSorted[i]));
    }
    for (auto& thread : threads) {
        thread.join();
    }
    tstop = std::chrono::high_resolution_clock::now();
    std::cout << " koniec, trwało " << std::chrono::duration_cast<std::chrono::milliseconds>(tstop-tstart).count()/1000.0 << " s\n";

    return 0;
}