===== [ekstra] Wątki w C++ ===== Język C+''''+ (od wersji C+''''+11) ma w bibliotece standardowej obsługę wątków. \\ API do obsługi wątków w C+''''+ zostało przeniesione z ''boost::thread'', z wyjątkiem obsługi przerywania wątków (''thread::interrupt()'')((W C+''''+20 pojawiła się klasa ''std::jthread'' która pozwala zasygnalizować wątkowi że powinien się przerwać, ale wywołanie ''request_stop'' __nie__ przerywa wywołań systemowych (np. ''read''). )). Przykład prostego programu:
#include
#include
void funkcja(unsigned count, const char *argument) {
while (count--)
printf("Hello %s\n", argument);
/* kod wykonywany w drugim wątku */
}
int main() {
std::thread t(funkcja, 3, "world");
/* kod wykonywany w pierwszym wątku */
t.join();
return 0;
}
++++ Odpowiednik kodu w C używający pthreads|
#include
#include
#include
struct funkcja_argumenty{
unsigned count;
const char * argument;
};
void * funkcja(void * rawArgs){
struct funkcja_argumenty *args = rawArgs;
while(args->count--)
printf("Hello %s\n", args->argument);
free(rawArgs);
/* kod wykonywany w drugim wątku */
return NULL;
}
int main(void){
pthread_t tid;
struct funkcja_argumenty *args = malloc(sizeof(struct funkcja_argumenty));
args->count = 3;
args->argument = "world";
pthread_create(&tid, NULL, funkcja, args);
/* kod wykonywany w pierwszym wątku */
pthread_join(tid, NULL);
return 0;
}
++++
Podsumowanie tworzenia wątków:
std::thread t1(funkcja); // tworzy obiekt 't1', tworzy nowy wątek i uruchamia w nim 'funkcja()'.
std::thread t2; // tworzy pusty obiekt 't2'.
t2 = std::thread(funkcja); /* tworzy nowy obiekt w miejscu "t2", niszczy poprzedni obiekt,
tworzy nowy wątek i uruchamia w nim 'funkcja()' */
// thread t3 = t2; // kod NIEPOPRAWNY – wątków nie można kopiować
thread t3 = std::move(t2); // ale wątki można przesuwać
std::thread t4(funkcja2, 1, 2, "z"); /* tworzy obiekt 't4', tworzy nowy wątek programu
i uruchamia w nim 'funkcja2(1, 2, "z")'*/
Podsumowanie zajmowania się wątkiem:
t1.join(); // czeka na zakończenie wątku
// thread * w = new thread(h); delete w; // niepoprawne – jeśli zmienna reprezentująca wątek
// ginie (''~thread::thread'') przed zakończeniem wątku,
// to program jest przerywany
// Jeśli główny wątek (main) się zakończy, pozostałe są zabijane (zachowanie inne niż np. w Javie)
t2.detach() // rozłącza wątek ze zmienną go reprezentującą. Można zmazać zmienną, wątek przeżyje.
std::thread(i).detach(); // tworzy nowy wątek bez deklaracji zmiennej która będzie go reprezentować.
W C+%%%%+ ustandaryzowano też zmienne lokalne dla wątku – w deklaracji zmiennej wystarczy dodać słowo kluczowe ''thread_local'', np:
thread_local int tid = i.fetch_add(1);
Ponadto, dodano API do zamków, zmiennych warunkowych, zmiennych atomowych i futures
* http://en.cppreference.com/w/cpp/atomic
* http://en.cppreference.com/w/cpp/thread
Przykład synchronizacji między wątkami (producent-konsument):
#include
#include
#include
#include
#include
std::mutex mtx;
std::condition_variable cv;
std::queue wiadomosci;
thread_local std::string wiadomosc;
void producer() {
while (true) {
std::cin >> wiadomosc;
{
std::unique_lock lock(mtx);
wiadomosci.push(wiadomosc);
cv.notify_one();
}
}
}
void consumer() {
while (true) {
{
std::unique_lock lock(mtx);
cv.wait(lock, [] { return !wiadomosci.empty(); });
wiadomosc = wiadomosci.front();
wiadomosci.pop();
}
std::cout << wiadomosc << std::endl;
}
}
int main() {
std::thread t(consumer);
producer();
}
++++ Odpowiednik kodu w C używający pthreads|
#include
#include
#include
#include
struct dumbQueue *msgQueue;
struct dumbQueue* dumbQueue_create (void);
void dumbQueue_push (struct dumbQueue *q, void *element);
void* dumbQueue_pop (struct dumbQueue *q);
int dumbQueue_isEmpty(struct dumbQueue *q);
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
_Thread_local char *msg;
void producer(void) {
while (1) {
msg = malloc(255);
scanf("%*[\n]");
scanf("%254[^\n]", msg);
{
pthread_mutex_lock(&mtx);
dumbQueue_push(msgQueue, msg);
pthread_cond_signal(&cv);
pthread_mutex_unlock(&mtx);
}
}
}
void *consumer(void *_) {
while (1) {
{
pthread_mutex_lock(&mtx);
while (dumbQueue_isEmpty(msgQueue))
pthread_cond_wait(&cv, &mtx);
msg = dumbQueue_pop(msgQueue);
pthread_mutex_unlock(&mtx);
}
printf("%s\n", msg);
free(msg);
}
}
int main(void) {
msgQueue = dumbQueue_create();
pthread_t tid;
pthread_create(&tid, NULL, consumer, NULL);
producer();
}
/*****/ struct dumbQueue {
/* T */ struct dumbQueue_e {
/* h */ void *element;
/* i */ struct dumbQueue_e *next;
/* s */ } *head, *tail;
/* */ };
/* i */
/* s */ struct dumbQueue *dumbQueue_create(void) {
/* */ struct dumbQueue *q = malloc(sizeof(struct dumbQueue));
/* j */ q->head = malloc(sizeof(struct dumbQueue_e));
/* u */ q->head->next = NULL;
/* s */ q->tail = q->head;
/* t */ return q;
/* */ }
/* a */
/* */ void dumbQueue_push(struct dumbQueue *q, void *element) {
/* d */ q->tail->next = malloc(sizeof(struct dumbQueue_e));
/* u */ q->tail = q->tail->next;
/* m */ q->tail->next = NULL;
/* b */ q->tail->element = element;
/* */ }
/* q */
/* u */ void *dumbQueue_pop(struct dumbQueue *q) {
/* e */ struct dumbQueue_e *next = q->head->next;
/* u */ free(q->head);
/* e */ q->head = next;
/* */ return q->head->element;
/* i */ }
/* m */
/* p */ int dumbQueue_isEmpty(struct dumbQueue *q) {
/* l */ return q->head->next == NULL;
/*****/ }
++++
===== [ekstra]
#include
#include
#include
using namespace std;
using namespace std::placeholders;
void b(char c){ cout << c << endl;}
class C{
public:
void operator()() {cout << 'C' << endl;}
void operator()(char c) {cout << c << endl;}
} objC;
class E{
const char * t_ = "ABCDEFGH";
public:
char f(int i){ cout << t_[i] << endl; return t_[i];}
void g(){t_="G";}
} objE;
int main() {
thread([]{cout << "A" << endl;}).join(); // lambda – http://en.cppreference.com/w/cpp/language/lambda
thread(b, 'B').join(); // "zwykła" funkcja
thread(objC).join(); // operator()
thread(objC, 'D').join(); // operator() z argumentami
thread(bind(&E::f, objE, 4)).join(); // funkcja na rzecz obiektu z ustalonym argumentem (currying)
thread(bind(&E::f, objE, _1), 5).join(); // funkcja na rzecz obiektu z nieustalonym argumentem
function funR(bind(&E::f, ref(objE), _1)); // funkcja na rzecz obiektu wołanego przez referencję
function funC(bind(&E::f, objE, _1)); // funkcja na rzecz obiektu w stanie z chwili tworzenia funcC
objE.g(); // modyfikacja obiektu
thread(funR, 0).join(); // wywołanie zmodyfikowanego obiektu - funcR zna obiekt przez referencję
thread(funC, 7).join(); // wywołanie zmodyfikowanego obiektu - funcC zna obiekt przez wartość
return 0;
}