Pokazywanie postów oznaczonych etykietą concurrency. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą concurrency. Pokaż wszystkie posty

12 lutego 2020

[C++17] Parallel algorithms

Biblioteka standardowa w C++17 została rozbudowana o algorytmy, których praca może zostać zrównoleglona. O sposobie wykonania decyduje ExecutionPolicy przekazane do funkcji jako argument. Programista musi zapewnić, że funkcja przekazana do "algorytmu", będzie bezpieczna - nie będzie zależności między danymi (np. modyfikowanie danych, które mogą być odczytywane przez inny równoległy wątek). Dobre wytłumaczenie różnic na stackoverflow, a tutaj małe zestawienie:
  • std::execution::seq - standardowe wykonanie sekwencyjne, bez zrównoleglenia.
  • std::execution::par - równoległe wykonanie (choć nie ma obietnicy, że tak się stanie).
  • std::execution::par_unseq - równoległe wykonanie (choć nie ma obietnicy, że tak się stanie). Wymaga silniejszych gwarancji na to że przeplatane wywołanie funkcji będzie bezpieczne także w obrębie jednego wątku. Nowe procesory oferują taką możliwość dzięki instrukcjom do wektoryzacji - SIMD (Single-Instruction, Multiple-Data) parallelism.
Przykład z funkcją std::reduce, która w działaniu przypomina std::accumulate. Zsumowanie wartości w wektorze:
#include <iostream>
#include <numeric>
#include <execution>
#include <vector>

using namespace std;

int main() {
    vector<int> vec{1, 2, 3, 4};

    int result = std::reduce(std::execution::par,
                             begin(vec),
                             end(vec));

    cout << result << endl;
}
W przypadku gcc (9.2.1 20191008) wymagane było zainstalowanie dodatkowej paczki libtbb-dev (Threading Building Blocks).
$ sudo apt-get install libtbb-dev
$ g++ -std=c++17 main.cpp -ltbb
$ ./a.out

10

11 lutego 2020

[C++] Atomics

Nie miałem do tej pory wiele do czynienia z atomic-ami (inny wpis) w prawdziwym życiu i traktuje je jako niskopoziomowy mechanizm, ale inni chyba lepiej potrafią wykorzystać ich możliwości. Pozwalają na pisanie kodu "lock-free", chociaż bez głębszego zrozumienia ich natury, niekoniecznie będzie to kod szybszy od tego opartego na muteksach. Ciekawy wykład na ich temat poprowadził Fedor Pikus na CppCon 2017: C++ atomics, from basic to advanced. What do they really do? Warto obejrzeć więcej niż raz.

Operacje na atomic-ach odzwierciedlają operacje sprzętowe i gwarantują, że zostaną wykonane w jednej transakcji (atomowo). CPU oferuje sporą liczbę mechanizmów, które są z nimi związane, z tego też względu standardowa biblioteka jest całkiem rozbudowana. Atomic-iem, może być każdy prymitywny typ (tylko takie obiekty mogą pojawić się w rejestrach CPU).
Przykłady:
// Dla
std::atomic<int> x{0};

// Operacje:
++x;           // atomowy pre-increment
x++;           // atomowy post-increment
x += 1;        // atomowy increment
int y = x * 2; // atomowy odczyt x
x = y + 2;     // atomowy zapis do x

// Uwaga, ta operacja jest niewspierana 
x *= 2;        // ERROR

// Atomowy odczyt x, po którym następuje atomowy zapis do x (dwie operacje)
x = x * 2;
W przykładzie poniżej, atomic posłużył do blokowania wątków, tak aby funkcje even/odd drukowały naprzemiennie tekst w momencie inkrementacji. Uwaga, nie ma gwarancji, że wartość counter wyświetlana na ekranie będzie zgodna z tym co było sprawdzane w if. Są to dwie atomowe operacje odczytu z pamięci, a wartość counter może się zmienić pomiędzy nimi.
#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

std::atomic<int> counter{0};

void odd(size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (counter % 2 == 1) {
            cout << "Odd  increment: " << counter << endl;
            counter++;
        } else {
            cout << "Odd  check: " << counter << endl;   // wartość mogła się zmienić
        }

        std::this_thread::sleep_for(std::chrono::milliseconds{20});
    }
}

void even(size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (counter % 2 == 0) {
            cout << "Even increment: " << counter << endl;
            counter++;
        } else {
            cout << "Even check: " << counter << endl;   // wartość mogła się zmienić
        }
        std::this_thread::sleep_for(std::chrono::milliseconds{40});
    }
}

int main() {
    constexpr size_t steps{6};
    std::thread t1{odd, steps};
    std::thread t2{even, steps};

    t1.join();
    t2.join();
}

Wynik:
Odd  check: 0
Even increment: 0
Odd  increment: 1
Even increment: 2
Odd  increment: 3
Odd  check: 4
Even increment: 4
Odd  increment: 5
Odd  check: 6
Even increment: 6
Even check: 7
Even check: 7

20 października 2016

[python] Prosty skrypt z uruchomieniem wątku.

Jak mówi Internet CPython w związku z istnieniem mechanizmu Global Interpreter Lock (GIL) nie pozwala na wykonanie więcej niż jednego wątku:
CPython implementation detail: In CPython, due to the Global Interpreter Lock, only one thread can execute Python code at once (even though certain performance-oriented libraries might overcome this limitation). If you want your application to make better use of the computational resources of multi-core machines, you are advised to use multiprocessing or concurrent.futures.ProcessPoolExecutor. However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously.
Niemniej jednak Python posiada stosowne API, które da się wykorzystać w innym interpreterze bez ograniczeń GIL, albo przy zadaniach typu I/O.

Queue (kolejka) zapewnia bezpieczny mechanizm do wymiany informacji pomiędzy wątkami.
  • Każde zawołanie Queue.put() zwiększa licznik, który jest dekrementowany w momencie zawołania Queue.task_done().
  • Główny wątek jest blokowany przez Queue.join(), do momentu, gdy wszystkie elementy z kolejki zostaną przetworzone.
  • Funkcja Queue.get() jest domyślnie blokująca, tzn. wątek zawiesza działania, jeżeli nie ma już danych do przetworzenia.
  • Thread.daemon - wszystkie wątki potomne zostaną zabite wraz z wątkiem głównym.
Często po wywołaniu programu na konsoli pojawia się informacja o wystąpieniu wyjątku. Jest to związane ustawieniem daemon=true. Interpreter nie zdążył z czystym zamknięciem wątku gdy kończony jest wątek główny. Rozwiązaniem byłby mechanizm w samum wątku, pozwalający na jego zakończenie, gdy nie jest on już potrzebny. W przypadku prostych skryptów, raczej nie trzeba się tym przejmować.
# -*- coding: utf-8 -*-

from Queue import Queue
from threading import Thread, current_thread

def worker():
    while True:
        item = q.get()
        print current_thread().name + ': ' + str(item[0] + item[1])
        q.task_done()

q = Queue()
num_worker_threads = 3

for i in range(num_worker_threads):
    t = Thread(target=worker)
    t.daemon = True
    t.start()

for item in [(1, 2), (4, 7), (5, 2)]:
    q.put(item)

q.join()
Wynik:
Thread-2: 3
Thread-2: 11
Thread-3: 7

13 stycznia 2016

[CppCon 2015] C++11/14/17 atomics and memory model: Before the story consumes you

Model pamięci jest największą zmianą jaką przyniósł nowy standard. A mechanizm atomic-ów na razie sprawiają mi najwięcej problemów. Być może znajdę kiedyś motywację, by bliżej się im przyjrzeć. W tej chwili wszystko co zrobiłem w tym kierunku to sucha lektura. Poniżej, bardzo fajny wykład z tegorocznego CppCon 2015 autorstwa Michaela Wonga, który rozjaśnił mi wiele kwestii.


17 października 2015

[C++11] std::future - programowanie oparte na zadaniach

W programowaniu współbieżnym preferowanym podejściem powinno być programowanie oparte na zadnich, a nie na wątkach [1]. Kilka przydatnych linków:
std::future jest obiektem, przechowującym wyniki (także wyjątki) asynchronie działających funkcji - czyta wynik ze współdzielonego stanu. Wynik można pozyskać za pomocą metody get(). W takim scenariuszu wątek główny zostanie zablokowany do chwili aż wynik będzie dostępny, lub zwróci wynik natychmiast, jeżeli asynchroniczna operacja zakończyła się wcześniej.

Do asynchronicznego wołania funkcji służy std::async. Jest to rozwiązanie najbardziej wysokopoziomowe, gdyż sam std::async dba o to by ustawić std::future w stan ready. Wywołanie oprócz ewentualnych argumentów jakie mają być przekazane do funkcji wymaga również podania policy:
  • std::launch::async - nowy wątek zostanie uruchomiony do wykonania zadania
  • std::launch::deferred - wykonanie zadania zostaje odroczone do momentu wywołania metod get() lub wait()
  • std::launch::async | std::launch::deferred - kombinacja flag, jednak zachowanie jest zależne od implementacji
Wszystkie przykłady kompilowałem w ten sam sposób, z dodatkowo włączonym sanitizerem, który pomógł wykryć kilka błędów w kodzie.
$ clang++ -std=c++14 -fsanitize=thread -lpthread -g main.cpp
Przykład obliczania ciągu Fibonacciego z zastosowaniem std::async.
#include <iostream>
#include <future>

using namespace std;

int fibonacci(int n)
{
    std::cout << "Current n: " << n << std::endl;
    if (n == 0)
        return 0;
    else if (n == 1)
        return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main()
{
    std::future<int> result = std::async(std::launch::async | std::launch::deferred,
                                         fibonacci, 3);
    std::cout << "Result: " << result.get() << std::endl;

    return 0;
}
Wynik:
Current n: 3
Current n: 2
Current n: 1
Current n: 0
Current n: 1
Result: 2
std::packaged_task także pozawala na tworzenie obiektów std::future. Można go porównać do std::function, a więc jako wrapper do tworzenia obiektów callable. W przeciwieństwie do std::async, nie uruchamia on jednak przekazanej funkcji automatycznie. Najważniejszą właściwością std::packaged_task jest możliwość pozyskania z niego obiektu std::future, i przekazania go do innego wątku, gdzie zostanie wykonany.

Przykład:
#include <iostream>
#include <thread>
#include <future>

using namespace std;

int fibonacci(int n)
{
    std::cout << "Current n: " << n << std::endl;
    if (n == 0)
        return 0;
    else if (n == 1)
        return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main()
{
    std::packaged_task<int(int)> task{fibonacci};
    std::future<int> result = task.get_future();

    std::thread t{std::move(task), 3};
    t.join();

    std::cout << "Result: " << result.get() << std::endl;

    return 0;
}
Wynik:
Current n: 3
Current n: 2
Current n: 1
Current n: 0
Current n: 1
Result: 2
std::promise można rozważyć jako początek kanału komunikacyjnego, na którego końcu znajduje się std::future. Do jego zadań, należy zapisanie wyniku do współdzielonego stanu. Czasami dane, które nas interesują są już dostępne (obliczone) i nie musimy czekać na zakończenie jakiegoś wątku. Za pomocą std::promise możemy w wątku głównym wytworzyć obiekt std::future (czyli końcówka kanału - dzięki metodzie get_future()), a następnie przekazać go (std::promise) do innego wątku. W wątku pobocznym, gdy interesujące nas dane zostaną obliczone wołamy metodę set_value() (początek kanału). W ten sposób obiekt std::future zostanie ustawiony w stan ready, i dane będą mogły być odczytane w wątku głównym. W porównywaniu do std::async, jest to rozwiązaniem bardziej nisko poziomowe, lecz czasami zachodzi potrzeba aby z niego skorzystać.

std::promise ma jeszcze jedno zastosowanie, można go wykorzystać jako mechanizm do sygnalizacji pomiędzy wątkami (std::promise<void> - typ obiektu jako void). Coś na kształt std::condition_variable, z tym że taka sygnalizacja może zadziać się tylko raz. Aby odblokować pracę na współdzielonych danych należy wywołać set_value(). Aby wstrzymać pracę wątku do momentu, aż dane będą dostępne, należy wywołać metodę wait().

Przykład:
#include <iostream>
#include <thread>
#include <future>

using namespace std;

struct Fibonacci {
    std::promise<int> p;
    bool isSatisfied;
    const int index;
    int partialIndex;

    Fibonacci(std::promise<int> p_, int index_)
        : p(std::move(p_))
        , isSatisfied(false)
        , index(index_)
        , partialIndex(0)
    {
    }

    int fibonacci(int n)
    {
        std::cout << "Current n: " << n << std::endl;

        int result = 0;
        if (n == 0)
            result = 0;
        else if (n == 1)
            result = 1;
        else
            result = fibonacci(n - 1) + fibonacci(n - 2);

        if (isSatisfied == false and partialIndex == n) {
            isSatisfied = true;
            std::cout << "Passing partial result" << std::endl;

            p.set_value(result);
        }
        return result;
    }

    void operator()(int partialVal_)
    {
        partialIndex = partialVal_;
        fibonacci(index);
    }
};

int main()
{
    std::promise<int> p;
    std::future<int> result = p.get_future();

    std::thread t{ Fibonacci{ std::move(p), 5 }, 3 };

    std::cout << "Result: " << result.get() << std::endl;
    t.join();

    return 0;
}
Przykładowy wynik:
Current n: 5
Current n: 4
Current n: 3
Current n: 2
Current n: 1
Current n: 0
Current n: 1
Passing partial result
Current n: 2
Current n: 1
Current n: 0
Current n: 3
Current n: 2
Result: 2Current n: 
1
Current n: 0
Current n: 1
Bibliografia:
  • [1] Scotta Meyersa: Skuteczny nowoczesny C++. APN PROMISE SA, 2015. Rozdział 7, str. 289.
  • [2] Anthony Williams: C++ Concurency in Action. USA Manning publications Co., 2012. Rozdział 7, str. 67.

2 października 2015

[C++11] std::condition_variable i współdzielenie danych między wątkami

Praca na danych wymaga mechanizmu, dzięki któremu wątki będą mogły zablokować swoje działania do czasu, aż z danych nie będzie korzystał żaden inny wątek. Najprostszym i zarazem najmniej efektywnym rozwiązaniem może być pętla i próba założenia blokady na muteks. Biblioteka standardowa na szczęście oferuje znacznie lepsze rozwiązanie w postaci std::condition_variable.

std::condition_variable wymaga do działania muteksów i std::unique_lock (ze względu na efektywność). Pozwala to na większą elastyczność, gdyż muteks może zostać zablokowany i odblokowany w dowolnym momencie (std::lock_guard wykorzystuje RAII). Klasa posiada dwa rodzaje metod: wait_* (wait, waif_for, wait_until) oraz notify_* (notify_one, notify_all). Te pierwsze blokują działanie wątku do czasu, aż zostanie spełniony warunek który został przekazany do wait_*. Może to być lambda, punkt w czasie lub odcinek czasu. Natomiast notify_* zajmuje się wybudzaniem wątków.

Ciekawostka, o której dowiedziałem się z cppreference. Przed wołaniem notify_*, należy ręczenie zawołać unlock na obiekcie std::unique_lock (choć destruktor tego obiektu i tak by to wykonał). W ten sposób unikamy sytuacji, w której wybudzamy wątek, tylko po to by ponownie go uśpić, ponieważ warunek nie został jeszcze osiągnięty.
Przykład.
#include <condition_variable>
#include <thread>
#include <mutex>
#include <list>
#include <string>
#include <iostream>

using namespace std;

std::mutex m;
std::condition_variable cv;
std::list<string> train;

void dig_coal() {
    int resource = 4;
    while(resource >= 0) {
        std::unique_lock<std::mutex> l{m};
        cv.wait_for(l, std::chrono::milliseconds{400});

        if (resource > 0) train.push_back("coal");
        else train.push_back("empty");
        resource -= 1;
        std::cout << "Added trolley, train length: " << train.size() << std::endl;

        l.unlock();
        cv.notify_one();
    }

    std::cout << "No more coal to mining." << endl;
}

void burn_coal() {
    while(true) {
        std::unique_lock<std::mutex> l{m};
        cv.wait(l, []{ return not train.empty(); });

        const string trolley = train.front();
        train.pop_front();
        std::cout << "Coal burn, train length: " << train.size() << std::endl;

        l.unlock();
        cv.notify_one();

        if(trolley == "empty") break;
        std::this_thread::sleep_for(std::chrono::milliseconds{600});
    }

    std::cout << "All coal burn." << endl;
}

int main() {
    std::thread miner{dig_coal};
    std::thread power_station{burn_coal};

    cv.notify_one();

    miner.join();
    power_station.join();

    return 0;
}
Wynik:
Added trolley, train length: 1
Coal burn, train length: 0
Added trolley, train length: 1
Added trolley, train length: 2
Coal burn, train length: 1
Added trolley, train length: 2
Added trolley, train length: 3
No more coal to mining.
Coal burn, train length: 2
Coal burn, train length: 1
Coal burn, train length: 0
All coal burn.

3 września 2015

[C++11] Muteksy i współdzielenie danych między wątkami

Wstęp

Biblioteka standardowa C++11 wprowadza klasę std::lock_guard typu RAII implementującą idiom muteksu (blokuje muteks w konstruktorze i zwalnia go w destruktorze). Stosując muteksy należy przyswoić sobie dwie ważne zasady:
  1. Nie wolno zwracać lub zapisywać wskaźników lub referencji poza zasięgiem lock-a (blokady).
  2. Należy blokować zasoby tylko przez minimalny okres czasu, potrzebny do wykonania danej operacji.
Przykład poniżej pokazuje wykorzystanie std::lock_guard.
#include <iostream>
#include <string>
#include <thread>
#include <mutex>

using namespace std;

class Storage {
private:
    std::mutex interanl_mutex;
    std::string buff;

public:
    void increment(const std::string id) {
        std::lock_guard<std::mutex> guard(interanl_mutex);
        buff += id;
    }

    void show() {
        std::lock_guard<std::mutex> guard(interanl_mutex);
        std::cout << buff << endl;
    }
};

void update(const std::string id, int loop_counter, Storage& storage) {
    for (int i = 0; i < loop_counter; ++i) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        storage.increment(id);
    }
}

int main() {
    Storage s;
    std::thread t1{update, std::string("a"), 2, std::ref(s)};
    std::thread t2{update, std::string("b"), 3, std::ref(s)};

    t1.join();
    t2.join();

    s.show();
    return 0;
}
Wynik:
ababb

Deadlock - zakleszczenie

Z samym zagadnieniem można zapoznać się tutaj:
Kilka zasad dzięki, którym można uniknąć tego rodzaju problemów:
  1. Nie wolno czekać na inny wątek jeżeli istnieje szansa, że to on może czekać na nas.
  2. Nie wolno zakładać blokad, jeżeli jakaś została już założona. Jeżeli wątek potrzebuje kilku muteksów należy wykonać blokowanie jako pojedynczą operację (stosując np. std::lock)
  3. Jeżeli tworzona jest biblioteka, nie należy wołać kodu użytkownika trzymając jednocześnie blokadę na muteksach. Nigdy nie można być pewnym, czy kod użytkownika też nie założy swojej własnej blokady.
  4. Jeżeli nie można skorzystać z std::lock do założenia kilku blokad jednocześnie, należy pamiętać by blokady zakładać zawsze w tej samej kolejności.
  5. Nie wolno zakładać blokad, jeżeli kod posiada już blokadę na muteksa z niższego poziomu (można stworzyć hierarchical_mutex, który nie jest częścią standardu ale jest łatwy w implementacji [1])
W tym miejscu należy wspomnieć o nowej klasie std::unique_lock, którą można stosować tam gdzie należy odroczyć blokowanie muteksów, albo gdy istnieje potrzeba przetransferowania własności do innego obiektu. Ma ona podobny konstruktor do std::lock_guard.
Drugi parametr konstruktora może przyjmować kilka interesujących parametrów:
  • std::adopt_lock jest używany by zaznaczyć, że muteks został już zablokowany i std::lock_guard albo std::unique_lock powinien tylko zaadoptować własność, zamiast próbować go jeszcze raz blokować.
  • std::defer_lock występuje tylko dla std::unique_lock i mówi o tym że muteks nie powinien zostać zablokowany przez konstruktor. Można tą operację wykonać później wołając np. funkcję std::lock.
Dwa przykłady jak może wyglądać zakładanie kilku blokad jednocześnie:
std::mutex mutex1;
std::mutex mutex2;

std::lock(mutex1, mutex2);
std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock);
Alternatywa z wykorzystaniem std::unique_lock:
std::mutex mutex1;
std::mutex mutex2;

std::unique_lock<std::mutex> lock1(mutex1, std::defer_lock);
std::unique_lock<std::mutex> lock2(mutex2, std::defer_lock);
std::lock(lock1, lock2);

Inicjowanie zasobów współdzielonych przez wątki

Często zachodzi potrzeba zainicjowania zmiennej tylko przez jeden z wątków. Zamiast samodzielnie tworzyć kod blokujący zasób i sprawdzający czy został on zainicjowany, biblioteka standardowa wprowadza specjalny mechanizm w postaci std::call_flag i std::call_once, które pozwalają na wykonanie takiej operacji (inicjowania) dokładnie raz w bezpieczny sposób.

W C++11 istnieje konieczność stosowania std::ref w przypadku argumentów do funkcji inicjującej, inaczej kompilator zaprotestuje. Zostało to naprawione w C++17.
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>

using namespace std;

std::mutex vec_mutex;
std::once_flag vec_flag;

void show(std::string description, std::vector<int>& vec) {
    std::cout << description;
    for(const auto& v : vec)
        std::cout << v << " ";
    std::cout << std::endl;
}

void init(std::vector<int>& vec) {
    for(auto& v : vec)
        v = 0;
    show("Initialize (call_once):    ", vec);
}

void update(std::vector<int>& vec, int index) {
//  std::call_once(vec_flag, init, std::ref(vec));   // Działa w C++11
    std::call_once(vec_flag, init, vec);             // Działa w C++17
    std::lock_guard<std::mutex> guard(vec_mutex);
    vec[index] += 1;
}

int main() {
    std::vector<int> vec = {9, 9, 9};
    show("Creation (in main thread): ", vec);

    std::thread t1{update, std::ref(vec), 0};
    std::thread t2{update, std::ref(vec), 2};

    t1.join();
    t2.join();

    show("All thread finish:         ", vec);

    return 0;
}
Wynik:
Creation (in main thread): 9 9 9 
Initialize (call_once):    0 0 0 
All thread finish:         1 0 1 

Bibliografia

  • [1] Anthony Williams: C++ Concurency in Action. USA Manning publications Co., 2012. Rozdział 3, str. 33.

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

14 maja 2015

[C++] std::thread is low level primitive

Na obiektach std::thread zawsze należy wołać join() lub detach(), a brak decyzji skutkuje poważnym błędem. Operacje tworzenia obiektu i "decyzja" o jego losie są rozłączne, co niechybnie zachęca do popełniania pomyłek. Większość książkowych przykładów zachęca do pisania własnych wraperów typu RAII, ale dlaczego sam standard nie oferuje czegoś podobnego? Odpowiedź przyniósł bardzo fajny wpis na blogu Andrzeja Krzemieńskigo.
std::thread należy rozpatrywać jako nisko poziomowy (low level) prymityw. std::thread jest tylko budulcem do tworzenia wysoko pozimowych rozwiązań, tak jak delete, jest budulcem, dzięki któremu może istnieć std::unique_ptr. W momencie wprowadzania tego rozwiązania komitetowi standaryzacyjnemu po prostu zabrakło czasu, na coś więcej. Tak jak należy wystrzegać się delete, w produkcyjnym kodzie, tak samo powinno być traktowane std::thread. Być może boost::strict_scoped_thread będzie dobrym rozwiązaniem.
#include <iostream>
#include <thread>
#include <boost/thread/scoped_thread.hpp>

void f()
{
    std::cout << "Hello" << std::endl;
}

int main()
{
    std::thread t1(f);
    t1.join();

    boost::strict_scoped_thread<> t2((boost::thread(f)));  // podwójne nawiasy

    return 0;
}
I jeszcze konfiguracja CMakeLists.txt
project(cpp_thread)
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

find_package(Boost COMPONENTS system thread REQUIRED)
target_link_libraries(${PROJECT_NAME}
    pthread
    ${Boost_SYSTEM_LIBRARY}
    ${Boost_THREAD_LIBRARY}
)

3 listopada 2013

[C++11] Współbieżność

Pierwsze podejście do współbieżności, wprowadzonej do biblioteki standardowej wraz z nowym standardem.
#include <thread>
#include <iostream>

void hello()
{
    std::cout << "Hello World!" << std::endl;
}

int main()
{
    std::thread t(hello);
    t.join();
    return 0;
}
Było trochę kłopotów przy próbie kompilacji (gcc - 4.7.3, clang - 3.2-1). gcc potrzebuje dodatkowej flagi w postaci pthread, natomiast clang w wersji 3.2 jeszcze nie radzi sobie z wątkami, ale i na to znalazł się sposób.
Budowanie z linii poleceń:
$ g++ -pthread --std=c++11 main.cpp
$ clang++ -pthread --std=c++11 \
     -D__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1 \
     -D__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2 \
     -D__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 \
     -D__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 main.cpp
Poniżej ustawienie dla CMakeList.txt (cmake):
project(thread_hello)
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")