Datenübergabe an Threads

Inhaltsverzeichnis[Anzeigen]

Ein Thread kann seine Daten per Copy oder per Referenz erhalten. Per Default sollte der Thread seine Daten per Copy annehmen, da per Referenz angenommene Daten leicht ihre Gültigkeit verlieren.

Datenübergabe

Ein Thread ist ein Variadic Template. Das heißt insbesondere, dass er beliebig viele Daten annehmen kann. Der kleine Programmschnipsel stellt die zwei Variationen vor.

std::string s{"C++11"}

std::thread t([=]{ std::cout << s << std::endl;});
t.join();

std::thread t2([&]{ std::cout << s << std::endl;});
t2.detach()

 

Genau genommen nimmt in dem Beispiel nicht der Thread, sondern die Lambda-Funktion die Daten an. Das ändert aber nichts an meiner Argumentation. So nimmt der Thread t seine Daten per Copy  ([=]), so nimmt der Thread t2 seine Daten per Referenz an ([&]).

Welche Gefahren verbergen sich in den paar Zeilen? Thread t2 nimmt sein String s per Referenz an uns löst sich von der Lebenszeit seines Erzeugers. Zum einen ist die Lebenszeit des String s an die Lebenszeit seines Aufrufkontextes gebunden, zum anderen endet die Lebenszeit des globalen Objekts std::cout mit der Lebenszeit des main-Thread. Deren Lebenszeit kann aber deutlich kürzer sein als die des Threads t2, da dieser sich durch den Aufruf t2.detach() von der Lebenszeit des main-Thread gelöst hat.

Immer noch nicht überzeugt? Das folgende Programm bringt die Problematik auf den Punkt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <chrono>
#include <iostream>
#include <thread>

class Sleeper{
  public:
    Sleeper(int& i_):i{i_}{};
    void operator() (int k){
      for (unsigned int j= 0; j <= 5; ++j){
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        i += k;
      }
      std::cout << std::this_thread::get_id() << std::endl;
    }
  private:
    int& i;
};


int main(){

  std::cout << std::endl;

  int valSleeper= 1000;
  std::thread t(Sleeper(valSleeper),5);
  t.detach();
  std::cout << "valSleeper = " << valSleeper << std::endl;

  std::cout << std::endl;

}

 

Welchen Wert besitzt die Variable valSleeper, wenn sie in der Zeile 27 ausgegeben wird? valSleeper ist eine globale Variable. Der Thread t erhält als Arbeitspaket ein Funktionsobjekt vom Typ Sleeper zusammen mit der Variable valSleeper und die Zahl 5 (Zeile 25). Die entscheidende Beobachtung ist es, dass der Thread valSleeper per Referenz (Zeile 7) erhält und sich von der Lebenszeit seins Erzeugers löst (Zeile 26). Wird der Thread ausgeführt, prozessiert er den Klammer-Operator des Funktionsobjektes (Zeile 8 - 14). In diesem zählt er von 0 bis 5, schläft in jeder Iteration für 1/10 Sekunde und inkrementiert i um k. Zum Abschluss gibt er seine ID aus. Nach Adam Riese ergibt das 1000 + 6 * 5= 1030. 

Da scheint sich wohl jemand verrechnet zu haben. Die Ausgabe des Programms ergibt nur 1000.

Sleeper

Was läuft hier schief? Weder besitzt valSleeper den Wert 1030, noch gibt der Thread seine ID aus. Das Programm besitzt undefiniertes Verhalten. Die Lebenszeit des main-Threads endet, bevor der Kinderthread die Variable fertig inkrementiert hat und seine ID auf std::cout schreibt. Wird die Lebenszeit des Kinderthreads durch t.join() in Zeile 26 an die Lebenszeit seines Erzeugers gebunden, stellt sich das erwartete Ergebnis ein.

int main(){

  std::cout << std::endl;

  int valSleeper= 1000;
  std::thread t(Sleeper(valSleeper),5);
  t.join();
  std::cout << "valSleeper = " << valSleeper << std::endl;

  std::cout << std::endl;

}

 

Sleeper2

Eine große Herausforderung besitzen Threads. Sowohl der Kinderthread als auch der main-Thread teilen sich zwei Objekte. Das ist zum einen die Variable valSleeper, das ist zum anderen das globale Objekt std::cout. Sobald mehrere Threads auf eine gemeinsame Variable gleichzeitig zugreifen und zu mindestens einer der Threads versucht, diese zu modifizieren, gilt es diese Variable zu schützen. Dazu aber mehr im nächsten Artikel, der sich mit dem Teilen von Daten zwischen Threads  beschäftigt.

Hintergrundinformation

Variadic Templates
    Die Details zu Variadic Templates lassen sich in dem Linux-Magazin Artikel Punktlandung (08/2015) nachlesen.

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Hole dir dein E-Book. Unterstütze meinen Blog.

Kommentar schreiben


Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare