Open Source im professionellen Einsatz
Linux-Magazin 06/2016

Modernes C++ in der Praxis – Folge 28

Pünktlich verschlafen

Die neue Zeitbibliothek von C++11 erweist sich als elementarer Bestandteil der Threading-Schnittstelle: Sowohl Threads, Locks und Bedingungsvariablen als auch Futures haben ein Verständnis von Zeit. Dank ihrer Unterstützung kann ein Entwickler unterschiedliche Wartestrategien verfolgen.

1029

Gemein ist den vier Komponenten, dass sie für eine Zeitspanne oder bis zu einem gewissen Zeitpunkt schlafen, warten oder nur blockieren. Die Methoden rund um das Zeitmanagement in Multithreading-Programmen folgen dabei einer einfachen Konvention. Methoden, die mit »_for« enden, parametrisiert C++11 mit einer Zeitdauer. Methoden, die mit »_until« enden, verwenden dafür einen Zeitpunkt. Details zu Zeitdauer und Zeitpunkten verrät ein Artikel aus dem Linux-Magazin [1]. Einen einfachen Überblick zu den verschiedenen Methoden liefert Tabelle 1. Sie beziehen in den Begriff "Warten" auch das Schlafen und Blockieren ein.

Tabelle 1

Methoden für absolutes und relatives Warten

Multithreading-Komponenten

Warten bis

Wartezeit

std::thread th

th.sleep_until(in2min)

th.sleep_for(2s)

std::unique_lock lk

lk.try_lock_until(in2min)

lk.try_lock_for(2s)

std::conditon_variable cv

cv.wait_until(in2min)

cv.wait_for(2s)

std::future fu

fu.wait_until(in2min)

fu.wait_for(2s)

std::shared_future shFu

shFu.wait_until(in2min)

shFu.wait_for(2s)

In der Tabelle bezeichnet »in2min« einen Zeitpunkt, der 2 Minuten in der Zukunft liegt, »2s« verweist auf eine Zeitdauer von 2 Sekunden. Während der Anwender aber »in2min« mit dem Ausdruck

auto in2min= std::chrono::steady_clock ::now() + std::chrono::minutes(2)

aufwändig definieren muss, gehört der Ausdruck »2s« dank C++14 automatisch als fundamentales Literal zum Repertoire. C++14 enthält zudem noch weitere fundamentale Literale zu typischen Zeitspannen, die CPP-Referenz unter [4] weiß mehr.

Wartestrategien mit Zukünften

Als zentrale Idee des etwas später beschriebenen Programms fungiert ein Promise [5]. Dieses Klassen-Template stellt den so genannten Futures üblicherweise Werte oder Exceptions zur Verfügung, die eine Future auf asynchrone Weise abholen. Im konkreten Fall greifen vier Futures [5] zu unterschiedlichen Zeitpunkten auf das Ergebnis des Promise zu, was jedoch nur mit geteilten Futures (»std::shared_future« ) funktioniert.

Zeitgeber in C++11

Bei drei verschiedenen Zeitgebern in C++11 stellt sich natürlich die Frage, worin diese sich unterscheiden.

  • »std::chrono::system_clock« : Stellt die natürliche Zeit (Wall Clock) dar. Er bietet die Hilfsfunktionen »to_time_t« und »form_time_t« [2] an, um Zeitpunkte in Datumsausgaben zu konvertieren.
  • »std::chrono::steady_clock« : Gibt als einziger Zeitgeber die Garantie, dass niemand ihn neu stellen kann. Damit ist »std::chrono::steady_clock« die ideale Uhr, um bis zu einem Zeitpunkt oder für eine bestimmte Zeitdauer zu warten.
  • »std::chrono::high_resolution_clock« : Ist der Zeitgeber mit der höchsten Auflösung. Er kann als Alias auf »std::chrono::system_clock« oder auch »std::chrono::steady_clock« implementiert sein.

Der C++-Standard gibt zwar keine Garantie für die Genauigkeit, den Startzeitpunkt oder den gültigen Zeitbereich, die ein Zeitgeber darstellen kann. Der Startzeitpunkt von »std::chrono:system_clock« ist aber typischerweise der 1. Januar 1970, die so genannte Unix-Zeit [3]. Bei »std::chrono::steady_clock« kommt mit Vorliebe der Bootzeitpunkt des Rechners zum Tragen.

Beim Warten auf das Ergebnis, das das Promise ausgibt, verfolgt jede Future eine andere Strategie. C++ führt alle Promises und Futures in separaten Threads aus. Der Einfachheit halber spricht der Text daher in der folgenden Aufzählung nicht mehr von Future, sondern von Threads, welche die Future besitzen.

  • »consumeThread1« : Wartet bis zu 4 Sekunden auf das Ergebnis des Promise.
  • »consumeThread2« : Wartet bis zu 20 Sekunden auf das Ergebnis des Promise.
  • »consumeThread3« : Erkundigt sich beim Promise, ob das Ergebnis vorliegt, und legt sich dann wieder 700 Millisekunden schlafen.
  • »consumeThread4« : Erkundigt sich beim Promise nach dem Ergebnis. Legt sich beim ersten Mal 1 Millisekunde schlafen und verdoppelt anschließend jeweils seine Schlafperiode.

Deutlich wird hier also, dass die geteilten »consumeThread« recht unterschiedliche Strategien verfolgen.

Auf ins Schlaflabor

Nun zu dem eigentlichen Programm, das in Listing 1 zu sehen ist. In der »main()« -Funktion generiert es zu Beginn das Promise (Zeile 83). Bevor der Autor das Promise in einen separaten Thread verschiebt (Zeile 85), erzeugt dieser eine Future (Zeile 84). Da das Promise keine Copy-Semantik unterstützt, kann das Programm es nur in den Thread schieben (per Move-Semantik, [6]). Das gilt hingegen nicht für die geteilten Futures in den Zeilen 87 bis 90, diese lassen sich in ihren Thread kopieren.

Die Hilfsfunktion »getDifference()« , die sich über die Zeilen 9 bis 13 erstreckt, kommt im Beispielprogramm gleich mehrfach zum Einsatz. Sie benötigt als Eingabeparameter zwei Zeitpunkte. Ihr Rückgabewert ist die vergangene Zeit in Millisekunden. Weiter geht es zu den einzelnen Threads:

  • »producerThread« : Er führt die Funktion »producer()« in den Zeilen 15 bis 19 aus. Die Funktion stellt nach einem fünfsekündigen Powernap das Ergebnis »2011« zur Verfügung. Genau auf dieses warten im weiteren Verlauf bereits die Futures.
  • »consumerThread1« : Führt die Funktion »consumer()« aus (Zeilen 21 bis 35). In ihr verharrt der Thread vom aktuellen Zeitpunkt ausgehend maximal 4 Sekunden (Zeile 23), bevor er sich wieder seiner Arbeit widmet. In dieser Zeit steht natürlich das Ergebnis des Promise noch nicht bereit.
  • »consumerThread2« : Auch er startet die »consumer()« -Funktion. In ihr wartet er vom aktuellen Zeitpunkt ausgehend maximal 20 Sekunden (Zeile 23), bis er seine Arbeit fortsetzt.
  • »consumerThread3« : Ruft die Funktion »consumePeriodically()« auf (Zeilen 37 bis 55). Der Thread schläft immer für 700 Millisekunden (Zeile 41) und erkundigt sich dann, ob das Ergebnis des Promise schon da ist (Zeile 42). Dank der Zeitangabe von 0 Sekunden (»std::chrono::seconds(0)« ) wartet er in diesem Fall ausnahmsweise nicht. Steht das Ergebnis der Berechnung bereit, gibt er es in Zeile 49 aus.
  • »consumerThread4« : Verwendet die Funktion »consumeWithBackoff()« (Zeilen 57 bis 77). Er schläft in der ersten Iteration 1 Millisekunde und verdoppelt bei jeder weiteren Iteration seine Schlafdauer. Ansonsten gleicht seine Strategie der des »consumerThread3« .

Nachdem der in der Aufzählung zuerst erwähnte Producer-Thread also ein Ergebnis generiert hat, schreiten die verschiedenen Consumer-Threads zur Tat, um es weiterzuverarbeiten. Dabei verfolgen sie allerdings ziemlich unterschiedliche Strategien. Einige warten nur kurz, andere etwas länger und wieder andere halten kurze Nickerchen von gleichbleibender oder sich steigernder Länge zwischen ihren Anfragen.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • C++11

    Die neue Zeitbibliothek ist ein elementarer Baustein nicht nur für die Mulithreading-Fähigkeit von C++. Mit ihrer Hilfe legt der Entwickler einen Thread bis zu einem definierten Zeitpunkt schlafen oder fordert auf gut Glück ein Lock für eine Zeitspanne an.

  • C++11

    2014 ist ein besonderes Jahr für C++. Drei Jahre nach C++11 erfährt der Sprachstandard mit C++14 den letzten Feinschliff. Neben generischen Lambda-Funktionen und der vereinfachten Ermittlung des Rückgabetyps kann C++14 vor allem mit einem Feature punkten: Reader-Writer-Locks.

  • Reichhaltiges Angebot

    C++0x bringt nicht nur Veränderungen in der Kernsprache. Die Standardbibliothek der C++-Neuausgabe hat Multithreading, asynchrone Funktionsaufrufe, reguläre Ausdrücke und vieles mehr im Angebot.

  • C++

    Hochperformanten Code zu schreiben, den nur Eingeweihte zu würdigen wissen, ist für Entwickler ein bisschen so, wie für Formel-1-Testpiloten mit einem neuen Motor den Rundenrekord auf dem Nürburgring zu knacken. Doch oft endet der Tuningversuch in der Leitplanke.

  • C++11

    C++-Code sicherer machen und zugleich an der Performance-Schraube drehen, das sind die beiden Domänen der neuen Type-Traits-Bibliothek. Sie beschleunigt Code, indem sie Typen zur Kompilierzeit analysiert und verändert, wenn der Entwickler sie geschickt einsetzt.

comments powered by Disqus

Ausgabe 10/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.