30 maja 2015

[C++11] Przekazywanie zmiennych do wątku

Najpierw kilka przydatnych metod i obiektów do eksperymentowania z std::thread i std::this_thread.
  • hardware_concurrency - informuje ile wątków ze wsparciem sprzętowym jest dostępnych w systemie. Jeżeli planujemy zrównoleglić naszą pracę, trzeba pamiętać, że jeden wątek jest już zajęty przez nasz program.
  • get_id - metoda wołana z std::this_thread albo na obiekcie wątku. Pozwala zorientować się z jakim wątkiem mamy do czynienia. Standard nie definiuje co to będzie, ale gwarantuje, że taki obiekt (std::thread::id), może być porównywany i wyświetlany na standardowym wyjściu.
  • sleep_for - pozwala uśpić wątek na pewien czas.

Konstruktor std::thread ma podobną konstrukcję do std::bind. Trzeba pamiętać, że domyślnie wszystkie przekazane zmienne są kopiowane. Gdy interesuje nas aby wątek wyliczył i zapisał jakąś wartość do zmiennej przekazanej przez referencję, należy skorzystać z std::ref (tak jak w przypadku std::bind).
#include <iostream>
#include <thread>

using namespace std;

struct Presenter {
    void operator()(int& value) {
        cout << "Slave id: " << std::this_thread::get_id() << endl;
        value += 100;

        std::this_thread::sleep_for(std::chrono::seconds(2));
    }
};

int main() {
    cout << "Possible threads: " << std::thread::hardware_concurrency() << endl;
    cout << "Master id: " << std::this_thread::get_id() << endl;

    int value = 1;

    std::thread t{ Presenter(), std::ref(value) };
    cout << "Child id from master: " << t.get_id() << endl;

    t.join();
    return 0;
}
Wynik:
Possible threads: 4
Master id: 140557572298560
Child id from master: 140557555422976
Slave id: 140557555422976
Kłopotliwe mogą okazać się wszelkie niejawne rzutowania. W przykładzie poniżej, funkcja fun() przyjmuje jako argument std::string. Wykonanie programu będzie przebiegało w ten sposób. W pierwszej kolejności do wątku przekazany zostanie wskaźnik (char const*), a następnie wykonana zostanie konwersja do std::string (już w kontekście nowego wątku). Istnieje bardzo duża szansa, że gdy konwersja się zadzieje, zmienna z głównego wątku nie będzie już istnieć, a działanie programu będzie trudne do przewidzenia. Rozwiązaniem jest wykonanie jawnego rzutowania przed przekazaniem zmiennych do std::thread.
#include <iostream>
#include <string>
#include <thread>

using namespace std;

void fun(std::string s) {
    cout << "Received: " << s << endl;
}

int main() {
    char buf[10];
    for (int i = 0 ; i < 3; i++) {
        std::sprintf(buf, "%d", i);
        cout << "Setting: " << buf << endl;

        std::thread t{ fun, buf };
//      std::thread t{ fun, std::string(buf) };    // OK!

        t.detach();
    }
    std::sprintf(buf, "%d", 77);
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
Wynik. Jeden z wątków dokonuje konwersji z char* na std::string, gdy wątek główny zmienił już zawartość bufora na 77 (linijka 22).
Setting: 0
Setting: 1
Received: 1
Received: 2
Setting: 2
Received: 77

Brak komentarzy:

Prześlij komentarz