Zadanie - programowanie współbieżne - mutexy

0

Dobry wieczór, mam do wykonania pewne zadanie, oto jego treść:

Napisz program, który tworzy i wypełnia tablicę dwuwymiarową o wymiarach 30 x 6 liczbami pseudolosowymi z zakresu od 0 do 50. Wygenerowana tablica wyświetlana jest na standardowym wyjściu. Następnie:
• dla każdego wiersza uruchamiany jest nowy wątek, który oblicza wartość średnią elementów tego wiersza i zapisuje ją w tablicy jednowymiarowej Srednie[30].
• Dla każdego wiersza uruchamiany jest nowy wątek, który oblicza różnice wartosci elementów tablicy Tab[30][6] i wartości średniej z tablicy Srednie[30]. Wynik zapisywany jest w tablicy Roznice[30][6]. Na przykład: Roznice[1][2] = Tab[1][2] – Srednie[1];
• Dla każdego wiersza tablicy Roznice uruchamiany jest nowy wątek, który oblicza wartość średnią elementów tego wiersza. Wynik zapisywany jest w tablicy Odchylenia_standardowe[30]. Zauważ, że możesz (ale nie musisz) do tego wykorzystać tą samą funkcję obslugi wątku, która zapisywała wartości w tablicy Srednie.
Program powinien wyświetlać informację na standardowym wyjściu w następującym formacie:
Tablica Tab, wiersz 0: 1 43 5 2 6 0
Tablica Tab, wiersz 1: 4 3 6 12 6 10
….
Tablica Tab, wiersz 29: 4 7 23 3 6 9
Tablica Srednie, element 0: 9,5
Tablica Srednie, element 1: 6,83
Tablica Srednie, element 2: 8,67
….
Tablica Roznice, wiersz 0: …… <wartości >
Tablica Roznice, wiersz 1: …… <wartości >

Tablica Odchylenia_standardowe, element 0: …. <wartość>
Tablica Odchylenia_standardowe, element 1: …. <wartość>

Oto moje rozwiązanie:


#include <iostream>
#include <iomanip>
#include <time.h>
#include <thread>
#include <chrono>
#include <mutex>

std::mutex mtx;

const int MLP = 50;               // Maksymalna wartosc elementu tablicy
const int LDT = 30;               // Liczba wierszy
const int  LG = 6;                // Liczba kolumn

void srednia(int Tab[LDT][LG], float Srednie[LDT], int nr_wiersza)
{
    std::lock_guard<std::mutex> lock(mtx);

    float suma = 0;

    for (int kolumna = 0; kolumna < LG; kolumna++)
    {
        suma += Tab[nr_wiersza][kolumna];
    }

    Srednie[nr_wiersza] = (float)suma/LG;
    std::cout << "Tablica Srednie, element " << nr_wiersza <<": "<<Srednie[nr_wiersza] << std::endl;
}

void roznica(int Tab[LDT][LG], float Srednie[LDT], float Tab2[LDT][LG], int nr_wiersza)
{
    std::lock_guard<std::mutex> lock(mtx);

    std::cout << "Tablica Roznice, wiersz " << nr_wiersza << ": ";

    for (int kolumna = 0; kolumna < LG; kolumna++)
    {
        Tab2[nr_wiersza][kolumna] = Tab[nr_wiersza][kolumna] - Srednie[nr_wiersza];
        std::cout << std::setw(6) << std::setprecision(3) << Tab2[nr_wiersza][kolumna];
    }

    std::cout << std::endl;
}

void odchylenie(float Tab2[LDT][LG], float Odchylenia_standardowe[LDT], int nr_wiersza)
{
    std::lock_guard<std::mutex> lock(mtx);

    float suma = 0;

    for (int kolumna = 0; kolumna < LG; kolumna++)
    {
        suma += Tab2[nr_wiersza][kolumna];
    }

    Odchylenia_standardowe[nr_wiersza] = (float)suma/LG;;
    std::cout << "Tablica Odchylenia_standardowe, element " << nr_wiersza <<": " << Odchylenia_standardowe[nr_wiersza] << std::endl;
}

int main()
{
    int Tab[LDT][LG];       //wygenerowana tablica
    float Roznice[LDT][LG];    //tablica na wyniki roznicy
    float Srednie[LDT];     //tablica na srednie wiersza
    float Odchylenia_standardowe[LDT]; //tablica na warotsci odchylenia

    srand(time(NULL));

    for (int wiersz = 0; wiersz < LDT; wiersz++)
        for (int kolumna = 0; kolumna < LG; kolumna++)
        {
            Tab[wiersz][kolumna] = rand()% MLP + 1 ;
            Roznice[wiersz][kolumna] = 0;
        }

    std::cout << " Tablica Tab: " << std::endl;
    for (int wiersz = 0; wiersz < LDT; wiersz++)
    {
        for (int kolumna = 0; kolumna < LG; kolumna++)
            std::cout << std::setw(4) << Tab[wiersz][kolumna];
        std::cout << std::endl;
    }

    std::cout << std::endl;

    for (int wiersz = 0; wiersz < LDT; wiersz++)
    {
        std::cout << "Tablica Tab, wiersz " << wiersz << ": ";
        for (int kolumna = 0; kolumna < LG; kolumna++)
            std::cout << std::setw(4) << Tab[wiersz][kolumna];
        std::cout << std::endl;
    }

    std::cout << std::endl;

    std::thread srednie_threads[LDT];
    std::thread roznice_threads[LDT];
    std::thread odchylenia_threads[LDT];

    for (int i = 0; i < LDT; i++)
    {
        srednie_threads[i] = std::thread(srednia, Tab, Srednie, i);
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    std::cout << std::endl;

    for (int i = 0; i < LDT; i++)
    {
        roznice_threads[i] = std::thread(roznica, Tab, Srednie, Roznice, i);
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    std::cout << std::endl;

    for (int i = 0; i < LDT; i++)
    {
        odchylenia_threads[i] = std::thread(odchylenie, Roznice, Odchylenia_standardowe, i);
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    std::cout << std::endl;

    for (int i = 0; i < LDT; i++)
    {
        srednie_threads[i].join();
        roznice_threads[i].join();
        odchylenia_threads[i].join();
    }

    return 0;
}

screenshot-20200509210751.png
screenshot-20200509210803.png
screenshot-20200509210815.png

Na ostatnim screenie widać błąd, gdzie wypisywane są wartości tablic odchylenie_standardowe. Czy mógłby mi ktoś wyjaśnić czemu tak się dzieje?
Z góry dziękuje za pomoc
Pozdrawiam

0

Jaki to błąd? Jakie wartości powinny tam być?

0
kq napisał(a):

Jaki to błąd? Jakie wartości powinny tam być?

Na ostatnim screenie wartości tablicy 'odchylenie_standardowe' nie są poprawnie wyliczone. Cały czas są ta wpisywane te same, dziwne wartości. Sprawdzając szybko na kalkulatorze nie są one poprawne.

0

Pomijając, że synchronizujesz sleepem (nie ma żadnej innej zależności wykonania wątków liczących odchylenie od wątków liczących różnice, od których odchylenie zależy), to źle liczysz odchylenie: wzór to pierwiastek z sumy kwadratów różnic podzielonych przez liczbę elementów. Po zmianie kodu odchylenie() wyniki wychodzą bardziej realistyczne


void odchylenie(float Tab2[LDT][LG], float Odchylenia_standardowe[LDT], int nr_wiersza)
{
    std::lock_guard<std::mutex> lock(mtx);

    float suma = 0;

    for (int kolumna = 0; kolumna < LG; kolumna++)
    {
        suma += pow(Tab2[nr_wiersza][kolumna],2);
    }

    Odchylenia_standardowe[nr_wiersza] = sqrt(suma/LG);
    std::cout << "Tablica Odchylenia_standardowe, element " << nr_wiersza <<": " << Odchylenia_standardowe[nr_wiersza] << std::endl;
}

https://wandbox.org/permlink/1f2XJVxCxmpCrwF7

BTW: niby piszesz w C++, a bardzo czuć C.

0

Wiem, że wzór na odchylenie standardowe jest inny. Jednak polecenie mnie trochę myli, ponieważ jest tam napisane:

Dla każdego wiersza tablicy Roznice uruchamiany jest nowy wątek, który oblicza wartość średnią elementów tego wiersza. Wynik zapisywany jest w tablicy Odchylenia_standardowe[30].

Dlatego liczę to w ten sposób, bo nie wynika z tego, żeby policzyć odchylenie standardowe, a zapisać do tablicy o takiej nazwie wynik obliczeń. Chyba, że to błąd nauczyciela w treści zadania.

W jaki sposób mogę uzyskać wymagany efekt, nie używając funkcji sleep do synchronizacji i tworząc wątki w sposób pokazany poniżej (wszystko w jednej pętli)?

    for (int i = 0; i < LDT; i++)
    {
        srednie_threads[i] = std::thread(srednia, Tab, Srednie, i);
        roznice_threads[i] = std::thread(roznica, Tab, Srednie, Roznice, i);
        odchylenia_threads[i] = std::thread(odchylenie, Roznice, Odchylenia_standardowe, i);
    }
0

Musiałbyś pobawić się z futures/promises albo czymś poleconym przez kogoś bardziej obeznanego z tego typu wielowątkowością niż ja.

Imo to błąd w poleceniu, bo przez takie sumowanie tracisz dane (6 + -6 da 0, kiedy suma ich kwadratów to 72)

1

Powinieneś być dość łatwo w stanie zagwarantować kolejność wykonania wątków dla poszczególnych elementów tak, aby zachować kolejność.
Swoją drogą, nie spodziewałbym się synchronizacji 90 wątków na jednym mutexie. Raczej przewidziałbym synchronizację między wątkami dla poszczególnych wierszy tak, aby możliwe było wywołanie wszystkich wątków w jednej pętli. Sam mechanizm średnio się nadaje, jeśli nie jesteś w stanie zagwarantować kolejności wykonania wątków (co, jeśli scheduler wpuści wątek do liczenia średnich dopiero za wątkiem od różnic i odchylenia standardowego dla danego wiersza?).

0
zagura napisał(a):

Powinieneś być dość łatwo w stanie zagwarantować kolejność wykonania wątków dla poszczególnych elementów tak, aby zachować kolejność.

Czy mógłbyś bardziej podpowiedzieć jak mogę to uzyskać?

Swoją drogą, nie spodziewałbym się synchronizacji 90 wątków na jednym mutexie.

Sugerowałem, się poprzednimi ćwiczeniami przykładowymi. Jeden mutex - duża ilość wątków. Dlatego też tak zrobiłem to zadanie.

Raczej przewidziałbym synchronizację między wątkami dla poszczególnych wierszy tak, aby możliwe było wywołanie wszystkich wątków w jednej pętli.

    for (int i = 0; i < LDT; i++)
    {
        srednie_threads[i] = std::thread(srednia, Tab, Srednie, i);
        roznice_threads[i] = std::thread(roznica, Tab, Srednie, Roznice, i);
        odchylenia_threads[i] = std::thread(odchylenie, Roznice, Odchylenia_standardowe, i);
    }

Czy chodzi o taki zapis?

2

Metoda sleep_for jest bez sensu, bo niczego tutaj nie gwarantuje. Należy po każdym zadaniu poczekać na zakończenie pracy wątków, zanim rozpocznie się wykonywać kolejne zadania. Muteksy także są zbędne, bo zapis danych w tablicach nie musi być synchronizowany. Należy je usunąć a wyświetlanie przenieść w inne miejsce.

1
  1. Moje masło maślane. Napisałem, zanim odpowiednio przemyślałem:
  • Mutex sam w sobie gwarantuje tylko, że jeden wątek może być wykonywany w chronionej sekcji - który? - nie ma żadnej kontroli.
  • Mój pierwszy pomysł - condition variable - wątek liczący średnie ustawia cv dla różnic, a różnice cv dla odchyleń (czyli potrzebne są 2 cv dla każdego wiersza). W takim scenariuszu, jeśli wątek odchyleń rozpocznie się pierwszy, czeka na ustawienie się swojej zmiennej przez odpowiadający wątek różnic.
  • Pomysł KrzaQ - wykorzystanie promis - idea jest taka, że wątki zwracają "obietnicę" wyniku. Kiedy sam wynik jest potrzebny, ale nie jest dostępny - wątek czeka - jest to dość przeźroczyste w kodzie.
  1. Jeśli zbyt dużo wątków czeka, to program się obija, praktycznie nie masz żadnej korzyści, skoro tylko jeden wątek może wykonywać obliczenia.
  2. Tak, taką pętlę miałem na myśli :)
0

W odpowiedzi na komentarz użytkownika Miang. Utworzenie wątków średnie, następnie zakończenie ich pracy za pomocą .join(), utworzenie wątków różnić, zakończenie ich pracy .join(). Tak samo postąpiłem z wątkami odchyleń. Dzięki temu uzyskałem zamierzony wynik. Jednak co gdybym chciał utworzyć wszystkie wątki w jednej pętli, a drugą pętla służyłaby już tylko do podłączenia .join(). Chciałbym nie używać tutaj std::condition_variable ani future/promise , ponieważ jeszcze tego nie poznałem.

Hasła, o których było wspomniane w prezentacji, która otrzymałem: mutex, recursive_mutex, timed_mutex, recursive_timed_mutex, lock(), unlock(), try_lock(), lock_guard, unique_lock. Czy stosując wymienione, któreś z tych klas/metod jestem w stanie osiągnąć zamierzony efekt?

0

Może na samym początku w głównym wątku zrobić na jakimś mutexie lock() , a w watkach do liczenia różnic na początku też robić lock() tego mutexa (i od razu unlock() jak lock() się powiedzie) . W wątku głównym zrobić unlock() tego mutexa dopiero po join() wątków średnich. Zbyt elegancko to nie wygląda ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1