/*
* Copyright (c) Januar 2005 Jean Gressmann (jsg@rz.uni-potsdam.de)
*
* Einige Beispiele zur Benutzung der Thread-Klassen
*
*/
#ifdef _MSC_VER
#pragma warning(disable:4786)
#pragma warning(disable:4503)
#endif
#include <iostream>
#include <fstream>
#include <string>
#include <cassert>
#include <deque>
#include <list>
#include <ctime>
#include <cstdlib>
#include <cstdio>
#include <set>
#include <portablethreads/mutex.h>
#include <portablethreads/condition.h>
#include <portablethreads/thread.h>
#include <portablethreads/thread_pool.h>
#include <portablethreads/tsallocator.h>
#include <portablethreads/message_queue.h>
#include <portablethreads/utility.h>
#include <portablethreads/atomic_number.h>
#include <portablethreads/lock_free.h>
#ifdef _MSC_VER
#include <crtdbg.h>
#endif
using namespace std;
using namespace PortableThreads;
// Hello World Beispiel
//
// Ein Thread, der "Hello World" ausgibt, wird erzeugt.
// Um Threads benutzen zu können, muss die Klasse die "threadable"
// sein soll, von PThread erben und die pure virtual Methode
// void threadImpl(); überschreiben. In der Basisklasse PThread
// sind einige Methoden enhalten, die den Ablauf von Threads steuern.
//
// 1. bool run(); -> durch ein Aufruf dieser Methode wird ein
// Thread gestartet. Es ist durchaus möglich mehrere Threads
// pro Objekt laufen zu lassen (durch wiederholten Aufruf von run()),
// allerdings kann dann sehr schnell Verwirrung auftreten.
// Ich empfehle daher pro Thread ein eigenes Objekt anzulegen.
// HINWEIS: Die run()-Methode kehrt sofort zurück und wartet nicht,
// bis der Thread zum erstem Mal die threadImpl()-Methode betreten hat.
// 2. void Stop(); -> Bricht den laufenden Thread sofort ab. Diese
// Methode sollte benutzt werden um Threads vorzeitig zu beenden,
// da Threads, die auf diese Weise terminiert werden, DLLs, Dateien
// usw. in einem ungültigen Zustand hinterlassen können.
// 3. void join(); -> Wartet auf die Beendigung eines Threads. Diese
// Methode blockiert den Aufrufer so lange, bis der laufende Thread
// geendet hat. Diese Methode kann nur aufgerufen werden, wenn
// nicht bereits void Detach(); aufgerufen wurde.
// 4. void Detach(); -> Überführt einen Thread in den "detached" Zustand.
// In diesem Zustand können die Resourcen des Threads nach seiner
// Beendigung wiederverwendet werden. Allerdings kann dann auch nicht
// mehr auf die Beendigung des Threads mit void join(); gewaretet werden.
// 5. void Give(); -> Teilt dem laufenden Thread mit, den Prozessor für
// andere Threads freizumachen.
// 6. bool operator==(const PThread& o) const;
// bool operator!=(const PThread& o) const;
// Operatoren um Threads auf Gleichheit und Ungleichheit zu testen.
namespace Beispiel1
{
class HelloWorld : public PThread
{
// Einstiegsmethode für den Thread
void threadImpl()
{
cout << "Hello World" << endl;
}
};
}
void beispiel1()
{
cout << "Hello World Beispiel. ";
cout << "Press enter to start" << endl;
cin.get();
using namespace Beispiel1;
HelloWorld t;
// Startet den Thread
t.run();
// Wartet bis der Thread beendet ist. Wird nicht gewartet,
// erscheint höchstwahrscheinlich keine Ausgabe, da alle
// Threads mit dem Hauptthread (der main-Funktion) beendet
// werden.
t.join();
}
/*****************************************************************************/
// Weitere "Hello World" Beispiele
//
// In diesen Beispiel wird gezeigt, wie man Threads in den Griff bekommt,
// indem man sie mit Objektsemantik versieht. Objektsemantik bedeutet konkret,
// dass der Thread nach Aufruf der start()-Methode vor dem Ausführen des
// Destruktors wenigstens einmal läuft.
// PThreadObjektSemantik ist eine Policy-based Klasse. Die Policy spezifiziert
// wie oft der Thread den Funktionsoperator (= operator()) aufrufen soll. Es gibt drei
// fertige Policies:
// 1. runOnce -> Thread läuft höchstens einmal
// 2. template<unsigned int> runN -> Thread läuft höchstens N mal
// N kann auch 0 sein
// 3. runForever -> der Thread läuft so lange bis entweder der Destruktor
// von PThreadObjectSemantic aufgerufen wird, oder er mit shutdown()
// beendet wird.
// Es ist natürlich auch mögliche eingene Policies zu definieren. Policies sind
// Klassen, welche die folgenden drei Memberfunktionen bereitstellen müssen:
// 1. void reset(); -> versetzt die Policy in einen definierten Startzustand
// 2. bool next(); -> wird in jeder Iteration evaluiert. Über diese Methode
// wird gesteuert, wie oft der Thread läuft (genauer wie oft loopImpl()
// aufgerufen wird
// 3. void shutdown(); -> Nachdem diese Methode aufgerufen wurde, muss der
// nächste Aufruf von next() false liefern.
// Eine Klasse, die PThreadObjectSemantic nutzen möchte, muss über eine Standard-
// konstruktor verfügen UND den Funktionsoperator operator()() public definieren.
namespace Beispiel1_1
{
class HelloWorld
{
protected:
void operator()()
{
cout << "Hello World" << endl;
}
};
typedef PThreadObjectSemantic<HelloWorld, RunForever> HelloWorldForever;
class StopHelloWorld
{
HelloWorldForever& hello_;
protected:
void operator()()
{
hello_.shutdown(false);
}
public:
StopHelloWorld(HelloWorldForever& h)
: hello_(h)
{}
};
}
void beispiel1_1()
{
cout << "Hello World Beispiel mit einen Thread, der Objektsemantik besitzt. ";
cout << "Press enter to start" << endl;
cin.get();
using namespace Beispiel1_1;
// Default Policy ist runOnce
typedef PThreadObjectSemantic<HelloWorld> HelloWorldOne;
cout << "Ein mal Hello Word ausgeben" << endl;
try
{
HelloWorldOne hello;
hello.start();
}
catch(...)
{
assert(false && "This shouldn't have happened. Check implementation!");
}
// Das Template Argument zu runN gibt die Anzahl der Iterationen an
typedef PThreadObjectSemantic<HelloWorld, RunN<5> > HelloWorld5;
cout << "Fünf mal Hello Word ausgeben" << endl;
try
{
HelloWorld5 hello5;
hello5.start();
}
catch(...)
{
assert(false && "This shouldn't have happened. Check implementation!");
}
typedef PThreadObjectSemantic<StopHelloWorld> Stopper;
cout << "Für immer Hello World" << endl;
try
{
// Starte Thread ....
HelloWorldForever forever;
forever.start();
// der ewig läuft ....
Stopper stop(forever);
stop.start();
// jetzt haben wir genug und beenden den Thread
cout << "Genug Hello World" << endl;
}
catch(...)
{
assert(false && "This shouldn't have happened. Check implementation!");
}
}
/*****************************************************************************/
// PingPong Beispiel - ein Klassiker
//
// In diesem Beispiel geht es um rudimentäre Threadsynchronisation.
// Zwei Threads, Ping und Pong, spielen sich gegenseitig einen
// Ball zu (hier einen bool-Variable). Da immer nur ein Thread den
// Ball gleichzeitig haben darf, muss der Zugriff synchronisiert werden.
// Die Snychronisation wird durch das Dreiergespann TSAllocator, TSProxy
// und (No)LockPointer realisiert.
//
// TSAllocator - Allokation
// Da selbst die Datenallokation mit Threads
// schwierig werden kann, übernimmt die Klasse TSAllocator diese Aufgabe.
// Soll z.B. der bool allokiert werden so wird die Create()-Methode von
// TSAllocator aufgerufen. Der bool'sche Wert wird thread-safe allokiert
// und unter einem Schlüssel gespeichert. Der Datentyp des Schlüssels
// kann über das Template Argument der TSAllocator Klasse angegeben werden.
// Datentypen, die über den TSAllocator verwaltet werden sollen müssen
// zwingend über einen Copy-Konstruktor verfügen.
// Um die allokierte Variable zu nutzen ruft man die Methode Reference()
// auf. Sie liefert eine "Referenz" auf die Variable zurück.
// Eine nicht länger benötigte allokierte Variable kann durch einen Aufruf
// von Destroy() wieder gelöscht werden.
// Der TSAllocator sollte aber nie direkt genutzt werden, sondern nur
// indirekt durch die Klasse TSProxy.
//
// TSProxy - Allokation, Deallokation
// TSProxy ist dient dazu Variablen, deren Zugriff synchronisiert werden muss
// thread-safe anzulegen, zu referenzieren und zu löschen.
// Der Konstruktor von TSProxy
// template<Schlüsseltyp, Werttyp> TSProxy(Schlüssel, const Wert& copy, TSAllocator alloc)
//
// erzeugt eine Variable vom Typ >>Werttyp<< unter dem Schlüssel >>Schlüssel<<.
// Diese Variable existiert so lange im TSAllocator, bis das letzte (referenzgezählt)
// TSProxy-Objekt, welches genau diesen Schlüssel verwendet, zerstört wird.
// Jedes TSProxy-Objekt dient quasi als Anker für die allokierte Variable.
// Mittels der TSProxy Klasse können verschiedene Threads ein und die selbe Variable
// referenzieren, ohne dass der genaue Allokationsort (also die Adresse)
// den Threads bekannt sein muss. Es ist damit nicht mehr notwendig,
// jedem Thread einen Zeiger auf die Variable o.ä. mitzugeben.
// Im Konstruktor muss nur der Schlüssel angegeben werden. Die anderen
// Parameter haben default-Werte. Wird kein Wert (Parameter 2) angegeben,
// ruft TSProxy den Standard-Konstruktor für diesen Datentyp auf. Der letzte
// Parameter spezifiziert, welcher TSAllocator genutzt werden soll. Wird kein
// Allocator angegeben, wird der globale TSAllocator TSA genutzt.
// Der Konstruktor von TSProxy erzeugt also entweder ein neues Objekt
// in einem TSAllocator und/oder erhöht dessen Referenzzähler.
// Nachdem mit dem TSProxy eine "Referenz" auf eine Variable angelegt wurde
// kann mit den Klassen (No)LockPointer diese benutzt werden.
//
// Zugriff - (No)LockPointer
// Den eigentlichen Zugriff auf die allokierte und referenzierte Variable erhält
// man durch die die Klassen LockPointer und NoLockPointer. Beide Klassen
// erhalten im Konstruktor eine Referenz auf einen TSProxy. Über diese Referenz
// werten sie die Referenz aus und gelangen zu EXKLUSIVEM (LockPointer) und
// NICHT EXKLUSIVEM (NoLockPointer) Zugriff auf die Variable. Im Falle von
// LockPointer bleibt die gemeinsame Variable für die Lebenszeit des
// LockPointer-Objekts für andere Threads gesperrt.
// Warum NoLockPointer? Manche Klasse sind von sich aus synchronisiert, das
// heißt gegen gleichzeiten Zugriff geschützt (gilt für keine STL Klasse).
// Für Objekte dieser Klassen eine zusätzliche Synchronisation mittels
// LockPointer zu sichern ist ein unnötiger Overhead.
namespace Beispiel2
{
// Konstante zum Referenzieren der globalen Variablen
// Hinweis: Hier, wie auch in allen anderen Beispielen, wird der
// globale TSAllocator TSA genutzt.
const string PINGPONG = "PingPong";
class Ping : public PThread
{
void threadImpl()
{
// Erzeuge/referenziere die gemeinsame booleansche
// Variable. In diesem Fall ist es unwichtig,
// welcher Thread die Variable letztendlich
// erzeugt.
TSProxy<string, bool> sharedVar(PINGPONG);
for(int i = 0; i < 10; ++i)
{
// Synchronisierten Zugriff auf die Variable...
// Falls das Flag nicht setzt ist, war
// der Pong-Thread noch nicht dran, also
// warten wir.
// Die gemeinsame bool'sche Variable wird
// nur zum Testen ihres Wertes gesperrt.
while(!*LockPointer<bool>(sharedVar))
give();
// exklusiven Zugriff auf die Variable sichern.
LockPointer<bool> lockedSharedVar(sharedVar);
// setze das Flag auf false und signalisiere
// dem Pong-Thread so, dass er dran ist.
*lockedSharedVar = false;
cout << "Ping " << *lockedSharedVar << endl;
// Hier wird der LockPointer lockedSharedVar zerstört
// und die bool'sche Variable für andere Threads
// freigegeben.
}
}
};
class Pong : public PThread
{
void threadImpl()
{
TSProxy<string, bool> sharedVar(PINGPONG);
for(int i = 0; i < 10; ++i)
{
// falls das Flag gesetzt ist, war
// Ping noch nicht dran ...
while(*LockPointer<bool>(sharedVar))
give();
LockPointer<bool> lockedSharedVar(sharedVar);
*lockedSharedVar = true;
cout << "Pong " << *lockedSharedVar << endl;
}
}
};
}
void beispiel2()
{
cout << "Ping Pong Beispiel mit einem booleanschen Flag. ";
cout << "Press enter to start" << endl;
cin.get();
using namespace Beispiel2;
Ping p1;
Pong p2;
// Starte Threads ...
// Obwohl der Ping-Thread vor dem Pong-Thread gestartet
// wird ist AUF KEINEN FALL garantiert, dass er auch
// vor dem Pong-Thread läuft.
p1.run();
p2.run();
p1.join();
p2.join();
}
/*****************************************************************************/
// PingPong Beispiel mit Condition Variablen
//
// In diesem Beispiel werden Condition Variablen um zwischen den
// beiden Threads zu kommunizieren. Jeder Thread hat eine eigene
// Conditionvariable um dem Partner zu zeigen, dass er "den Ball
// gespielt hat".
// Da die Klasse PCondition keinen Copy-Konstruktor hat, werden die
// zwei Variablen auf dem Heap angelegt und nur ein Zeiger über
// die TSProxy Objekte allokiert.
// Da die PCondition Klasse selbst thread-safe ist, muss der
// Zugriff auf die PCondition Variable nicht extra synchronisiert
// werden. Das heißt es kann NoLockPointer anstelle von LockPointer
// benutzt werden.
namespace Beispiel3
{
const string PING_COND = "PingCondition";
const string PONG_COND = "PongCondition";
class Ping : public PThread
{
void threadImpl()
{
// wenn diese Proxies angelegt werden, wurden die Zeiger
// auf die Condition Variablen bereits im TSAllocator
// angelegt (main). Diese Proxies erzeugen also nur Referenzen.
TSProxy<string, PCondition*> pingCondition(PING_COND);
TSProxy<string, PCondition*> pongCondition(PONG_COND);
for(int i = 0; i < 10; ++i)
{
// Hier muss nicht extra synchronisiert werden, da
// PCondition bereits von sich aus synchronisiert.
(*NoLockPointer<PCondition*>(pingCondition))->wait();
cout << "Ping 0" << endl;
(*NoLockPointer<PCondition*>(pongCondition))->signal();
}
}
};
class Pong : public PThread
{
void threadImpl()
{
TSProxy<string, PCondition*> pingCondition(PING_COND);
TSProxy<string, PCondition*> pongCondition(PONG_COND);
for(int i = 0; i < 10; ++i)
{
(*NoLockPointer<PCondition*>(pongCondition))->wait();
cout << "Pong 1" << endl;
(*NoLockPointer<PCondition*>(pingCondition))->signal();
}
}
};
}
void beispiel3()
{
cout << "Ping Pong Beispiel mit Condition Variablen. ";
cout << "Press enter to start" << endl;
cin.get();
using namespace Beispiel3;
// erzeuge zwei Condition Variablen auf dem Heap
// SmrtPtr ist ein einfachen managed pointer
// ähnlich dem std::auto_ptr
SmartPtr<PCondition> pingCond(new PCondition);
SmartPtr<PCondition> pongCond(new PCondition);
// erzeuge die Zeiger auf die PCondition Objekte im
// Allocator.
TSProxy<string, PCondition*> pingCondition(PING_COND, pingCond.get());
TSProxy<string, PCondition*> pongCondition(PONG_COND, pongCond.get());
// Setze die Condition Variable für den Ping Thread auf signalisiert.
// Dieser Schritt ist notwendig, damit kein Deadlock entsteht,
// da PConditions nach ihrer Erzeugung nicht signalisiert sind.
// Würde dieser Aufruf weggelassen, würden beide Threads, Ping
// und Pong auf das signalisieren ihrer Condition warten. Ein
// Deadlock wäre die Folge.
(*NoLockPointer<PCondition*>(pingCondition))->signal();
Ping p1;
Pong p2;
// Starte Threads
// Der Ping Thread (p1) wird zuerst laufen, da die Condition Variable pingCondition
// auf "signalisiert" gesetzt wurde
p2.run();
p1.run();
p1.join();
p2.join();
}
/*****************************************************************************/
// Producer/Consumer Beispiel
//
// Ein Thread (Producer) erzeugt eine Reihe von Items, die von
// anderen Threads, den Constumern verarbeitet werden. Die
// Schwierigkeiten bei diesem Beispiel sind natürlich die Synchronisation
// des verwendeten Puffers in dem die produzierten Items gespeichert werden
// bevor sie konsumiert werden. Der Puffer (hier ein std::list) kann nur
// eine bestimmte Anzahl von Elementen aufnehmen. Das bedeutet, das der
// Producer unterbrochen werden muss, wenn alle Speicherplätze im Puffer
// belegt sind, so dass die Consumer arbeiten können.
// Der Producer erzeugt insgesamt 10 Items. Jeweils fünf davon werden von
// jedem Consumer Thread verarbeitet. Die Consumer müssen auf den Producer
// warten, falls keine Items im Puffer enthalten sind.
namespace Beispiel4
{
const string BUFFER = "Bufferme";
// der Typ des Puffers
typedef list<int> BufferType;
class Producer : public PThread
{
// Bestimmt wie viele Items maximal im Puffer gespeichert
// werden dürfen. Bei einem STL Container ist das sicherlich
// eine künstliche Einschränkung, bei einem Array jedoch
// nicht.
unsigned maxItemsInQueue_;
// In dieser Methode versucht der Producer ein Item zu pro-
// duzieren und in dem Puffer zu speichern. Es gelingt im
// falls noch nicht maxItemsInQueue_ im Puffer enhalten sind.
bool try_produce(TSProxy<string, BufferType>& buffer, int& produced)
{
// Sperre Puffer für alle anderen Threads
LockPointer<BufferType> BufferPtr(buffer);
// teste ob noch Items produziert werden können
if(BufferPtr->size() < maxItemsInQueue_)
{
cout << "Producing item " << produced << endl;
BufferPtr->push_back(produced);
++produced;
return true;
}
else
{
cout << "Buffer full" << endl;
}
// Entsperre Puffer
return false;
}
void threadImpl()
{
// Referenzen auf Puffer holen
TSProxy<string, BufferType> buffer(BUFFER);
// zehn Items produzieren
for(int produced = 0; produced < 10;)
{
if(!try_produce(buffer, produced))
pt_milli_sleep(50);
}
}
public:
Producer(int maxItems)
: maxItemsInQueue_(maxItems)
{}
};
class Consumer : public PThread
{
int ID;
// Hier versucht ein Consumer ein Item zu verarbeiten. Sind
// noch Items im Puffer gelingt es im, ansonsten tut er
// nichts.
bool try_consume(TSProxy<string, BufferType>& buffer, int& itemsConsumed)
{
LockPointer<BufferType> BufferPtr(buffer);
// wenn noch Items vorhanden sind, konsumiere ein Item
if(!BufferPtr->empty())
{
cout << "Consumer <" << ID << "> Consuming item " << BufferPtr->front() << endl;
BufferPtr->pop_front();
++itemsConsumed;
return true;
}
else
{
cout << "Consumer <" << ID << "> No items to consume" << endl;
}
return false;
}
void threadImpl()
{
// Referenz anlegen
TSProxy<string, BufferType> buffer(BUFFER);
for(int itemsConsumed = 0; itemsConsumed < 5;)
{
// versuche Item zu konsumieren
if(!try_consume(buffer, itemsConsumed))
pt_milli_sleep(50);
}
}
public:
Consumer(int x) : ID(x) {}
};
}
void beispiel4()
{
cout << "Producer-Consumer Beispiel mit einer std::list als Puffer. ";
cout << "Press enter to start" << endl;
cin.get();
using namespace Beispiel4;
Producer p(2);
Consumer c1(1);
Consumer c2(2);
// starte Threads ...
c1.run();
p.run();
c2.run();
c1.join();
c2.join();
p.join();
}
/*****************************************************************************/
// Ein weiteres Producer Consumer Beispiel. In diesem
// Beispiel werden PMessageQueues benutzt um die Consumer
// über das Erzeugen neuer Items zu informieren.
// Hier gibt es keine maximale Anzahl von Items, die
// erzeugt werden können. Jeder Consumer Thread wird
// über eine eigene PMessageQueue mit Items versorgt,
// die er bearbeiten soll.
namespace Beispiel5
{
const string MQ1 = "MessageQueue1";
const string MQ2 = "MessageQueue2";
// Typ der MessageQueue
typedef PMessageQueue<int> IQueue;
class Producer : public PThread
{
void threadImpl()
{
// der Consumer braucht Referenzen auf die PMessageQueues
// aller Consumer, in diesem Fall 2
// Hier werden wieder nur Referenzen auf die Queues
// geholt, die PMessageQueues werden in main allokiert
TSProxy<string, SharedPtr<IQueue> > Queue0(MQ1);
TSProxy<string, SharedPtr<IQueue> > Queue1(MQ2);
// erzeuge Items und stecke sie in die jeweilige Queue
cout << "[Producer] producing 10 items" << endl;
for(int i = 0; i < 10; ++i)
{
// erzeuge Zufallszahl zwischen 0 - 100
srand(static_cast<unsigned>(time(0)));
int z = rand();
if(z < 0) z *= -1;
z %= 100;
cout << "Producing item " << z << endl;
// wenn die Zahl kleiner als 50 ist dann bekommt sie
// der erste Consumer, ansonsten der zweite.
if(z < 50)
// Da die PMessageQueue von sich aus den Zugriff thread-safe
// reguliert, reicht hier ein NoLockPointer
(*NoLockPointer< SharedPtr<IQueue> >(Queue0))->pushBack(z);
else
(*NoLockPointer< SharedPtr<IQueue> >(Queue1))->pushBack(z);
// Warte eine Viertelsekunde (bessere Zufallszahlen)
pt_milli_sleep(100);
}
}
};
class Consumer : public PThread
{
int ID;
bool Keeprunning;
void threadImpl()
{
// Die Consumer brauchen nur eine Referenz auf "ihre" PMessageQueue
TSProxy<string, SharedPtr<IQueue> > Queue(ID == 1 ? MQ1 : MQ2);
while(Keeprunning)
{
// wenn noch Items vorhanden sind, konsumiere ein Item
int Next = -1;
// versuche ein Item aus der Queue zu holen. Ist noch ein
// Item vorhanden, gibt popFront() true zurück und der
// Variable Next wurde der Wert des ersten Items aus der
// Queue zugewiesen.
if((*NoLockPointer< SharedPtr<IQueue> >(Queue))->popFront(Next))
{
cout << "Consumer <" << ID << "> Consuming item " << Next << endl;
}
else
{
// Kein Item in der Queue. Warte auf Nachricht
(*NoLockPointer< SharedPtr<IQueue> >(Queue))->waitForMessage();
}
}
}
public:
Consumer(int x) : ID(x), Keeprunning(true) {}
void Quit()
{
Keeprunning = false;
// entsperre den Consumer Thread falls dieser auf einen Nachricht
// wartet
TSProxy<string, SharedPtr<IQueue> > Queue(ID == 1 ? MQ1 : MQ2);
(*NoLockPointer< SharedPtr<IQueue> >(Queue))->pushBack(0);
}
};
}
void beispiel5()
{
cout << "Producer-Consumer Beispiel mit einer PMessageQueue pro Consumer. ";
cout << "Press enter to start" << endl;
cin.get();
using namespace Beispiel5;
// Allokiere zwei PMessageQueues für die zwei Consumer
// Da PMessageQueue keinen Copy-Konstruktor besitzt, muss
// die Queue auf dem Heap erzeugt werden.
TSProxy<string, SharedPtr<IQueue> > Queue0(MQ1, new IQueue);
TSProxy<string, SharedPtr<IQueue> > Queue1(MQ2, new IQueue);
Producer p;
Consumer c1(1);
Consumer c2(2);
// Starte beide Consumer und den Producer.
// Da zu Beginn noch keine Items in der Queue
// enthalten sind, werden beide Consumer VERMUTLICH
// warten müssen. Das ist aber nur der Fall, wenn
// sie auch beide vor dem Producer laufen!!!
c1.run();
c2.run();
p.run();
p.join();
c1.Quit();
c2.Quit();
c1.join();
c2.join();
}
/*****************************************************************************/
// Ein Beispiel für die Benutzung des ThreadPools
// In diese Beispiel geht es darum eine ThreadPool zu erzeugen und anschließend
// Requests zu erzeugen, welche die "docXXX.txt" Dateien aus diesem
// Verzeichnis in das Verzeichnis copy kopieren
// Die Requests werden von den Threads asynchron abgearbeitet, d.h. es herrscht
// keine zeitliche Korrelation zwischen der Rückkehr der process()-Methode
// und der eigentlichen Bearbeitung durch den einen Thread. Der Request wird
// lediglich zur Bearbeitung kopiert und dann ein Thread gescheduled. Wann dieser
// Thread jedoch läuft, ist nicht vorherzusagen.
namespace beispiel_6
{
// Definition des Requests
// Eine KOPIE dieser Struktor oder Klasse wird an den Funktor
// übergeben. Die Struktur muss den Request eindeutig beschreiben.
// In unseren Fall wollen wir einen Datei kopieren, d.h. es
// reicht aus die Quell- und die Zieldatei zu kennen.
struct Request
{
string from_;
string to_;
Request(const string& from, const string& to)
: from_(from)
, to_(to)
{}
};
// Definition des Funktors
// Dieser Funktor wird von den Threads des ThreadPools
// pro Request ausgeführt. Als Parameter bekommt er
// eine KOPIE des Requests (die Signator des Funktors
// erwartet const Reference, um zu verhindern, dass die
// Struktur ein drittes mal kopiert wird).
struct CopyOneFile
{
void operator()(const Request& req)
{
// öffne die Quelldatei ...
ifstream in(req.from_.c_str(), ios::binary);
if(!in)
{
// ... schade, hat nicht geklappt
cerr << "Could not open source file " << req.from_ << endl;
return;
}
// öffne und erstelle die Zieldatei ...
ofstream out(req.to_.c_str(), ios::binary | ios::trunc);
if(!out)
{
// ... schade, hat nicht geklappt
cerr << "Could not create copy " << req.to_ << endl;
return;
}
// öffne und erstelle die Zieldatei ...
cout << "Copying file " << req.from_ << " to " << req.to_ << endl;
out << in.rdbuf();
}
};
}
void beispiel6()
{
cout << "Mehrere Dateien mit einen Threadpool kopieren. ";
cout << "Press enter to start" << endl;
cin.get();
using namespace beispiel_6;
// erzeuge einen Threadpool mit 3 Threads
PThreadPool<CopyOneFile, Request> pool(3);
char from[100];
char to[100];
// kopiere 10 Dateien
for(int i = 1; i <= 10; ++i)
{
// erzeuge Quell- und Zieldateinamen
sprintf(from, "../original/doc%d.txt", i);
sprintf(to, "../copy/doc%d.txt", i);
// veranlasse Requestverarbeitung
pool.process(new Request(from, to));
cout << "Busy: " << pool.active() << ", size: " << pool.size() << endl;
}
}
/*****************************************************************************/
// In diesem Beispiel werden zwei Producer und zwei Consumer über eine
// sogenannte "lock-free" Queue synchronisiert. Im Gegensatz zur PMessageQueue
// muss die Datenstruktur nicht "gelockt" werden. Dies bringt einen entscheidenden
// Geschwindigkeitsvorteil, das das sperren über Mutexe einen (für Rechnerverhältnisse)
// recht zeitraubende Aktivität ist. Zudem können gleichzeitig mehrere Threads in die
// Queue schreiben bzw. aus der Queue lesen.
// In diesem Beispiel werden nackte Pointer auf Heap-Speicher in die Queue gesteckt. In
// einen richtigen Programm sollte man an dieser Stelle Smartpointer verwenden.
namespace beispiel_7
{
// Queue
typedef PLockFreeQueue<int*> Buffer;
class Producer : public PThread
{
public:
Producer(int id, Buffer& buffer, int runs, int add)
: id_(id)
, runs_(runs)
, buffer_(buffer)
, add_(add)
{}
private:
void threadImpl()
{
for(int i = 0; i < runs_; ++i)
{
const int idToProduce = i+add_;
// Produziere ein "Item" und füge es in die Queue ein...
buffer_.pushBack(new int(idToProduce));
printf("%d producing item %d\n", id_, idToProduce);
pt_milli_sleep(50);
}
}
private:
const int id_, runs_;
Buffer& buffer_;
int add_;
};
class Consumer : public PThread
{
public:
Consumer(int id, Buffer& buffer, int runs)
: id_(id)
, buffer_(buffer)
, runs_(runs)
{}
private:
void threadImpl()
{
for(int i = 0; i < runs_;)
{
int* item = 0;
// Versuche ein Item aus der Queue zu nehmen.
// Schlägt fehl, falls die Queue leer ist.
if(buffer_.popFront(item))
{
assert(item);
printf("%d consuming item %d\n", id_, *item);
delete item;
++i;
}
else
{
printf("%d empty\n", id_);
pt_milli_sleep(50);
}
}
}
private:
const int id_;
Buffer& buffer_;
const int runs_;
};
}
void beispiel7()
{
cout << "Producer-Consumer Beispiel mit 2 Producern und 2 Consumern. Die Threads schreiben in/lesen aus einer lock-free Queue. ";
cout << "Press enter to start" << endl;
cin.get();
using namespace beispiel_7;
Buffer buffer;
// Producer 1 erzeugt 20 Items mit Id 0 aufwärts
Producer producer1(1, buffer, 20, 0);
// Producer 2 erzeugt 20 Items mit Id 20 aufwärts
Producer producer2(2, buffer, 20, 20);
// Consumer 1 und 2 verbrauchen je 20 Items
Consumer consumer1(1, buffer, 20);
Consumer consumer2(2, buffer, 20);
consumer1.run();
consumer2.run();
producer1.run();
producer2.run();
producer1.join();
producer2.join();
consumer1.join();
consumer2.join();
}
/*****************************************************************************/
// Eine Barrier ist nützlich um Threads an einer bestimmten Stelle im Code so lange
// zu blockieren, bis einen gewisse Anzahl die Stelle erreicht hat. Dies kann zum
// Beispiel nützlich sein um das sequentiellen Starten von Threads von derem
// "Arbeitsbeginn" zu entkoppeln.
namespace beispiel_9
{
class WaitAtBarrier : public PThread
{
public:
WaitAtBarrier(int id, PBarrier& barrier)
: id_(id)
, barrier_(barrier)
{}
void threadImpl()
{
cout << "Thread " << id_ << " waiting at barrier" << endl;
barrier_.wait();
cout << "done\n";
}
private:
const int id_;
PBarrier& barrier_;
};
}
void beispiel9()
{
cout << "Barrier-Beispiel. Es wird eine Barriere für 5 Threads erzeugt und anschließend\n";
cout << "fünf Threads. Der letzte Thread entsperrt dabei die Barriere und alle 5 Threads\n";
cout << "können losrechnen. ";
cout << "Press enter to start" << endl;
cin.get();
using namespace beispiel_9;
// Erzeuge Barriere an der 5 Threads
// warten können.
PBarrier barrier(5);
WaitAtBarrier w1(1, barrier), w2(2, barrier), w3(3, barrier), w4(4, barrier), w5(5, barrier);
// Erzeuge nacheinander die Threads. Die
// ersten vier werden an der Barriere geblockt...
w1.run();
w2.run();
w3.run();
w4.run();
cout << "Now four threads are waiting at the barrier.\n";
cout << "Press enter to start the fifth thread that will unlock the barrier.\n";
cin.get();
w5.run();
// ...der 5. Thread entsperrt die Barriere wenn er
// PBarrier::wait aufruft. Die Barrier kehrt in
// ihren Ausgangszustand zurück.
cout << "Threads done waiting at barrier!\n";
w1.join();
cout << "Thread 1 joined!" << endl;
w2.join();
cout << "Thread 2 joined!" << endl;
w3.join();
cout << "Thread 3 joined!" << endl;
w4.join();
cout << "Thread 4 joined!" << endl;
w5.join();
cout << "Thread 5 joined!" << endl;
}
/*****************************************************************************/
// Dieses Beispiel demonstriert die class PTime (Zeitmessung) und PRandom
// (pseudo Zufallszahlen).
// PTime stellt akkurate Messergebnisse bis in dem Mikrosekunden-Bereich bereit und
// ist direkt als Stopuhr einsetzbar.
// PRandom ist eine Implementation der Mersenne Twister 32 Bit Zufallszahlen von
// Makoto Matsumoto und Takuji Nishimura. Sie sind ein sicherer Ersatz für die
// standard rand() fund srand() Funktionen, da sie als Klasse gekapselt sind
// und daher nicht synchronisiert werden müssen. Der gleichzeitige Zugriff auf
// ein und das selbe PRandom Objekt muss allerdings nach wie vor synchronsisiert
// werden, zwei verschiedene Objekte sind aber unabhängig.
namespace beispiel_10
{
class GenerateNumbers : public PThread
{
public:
GenerateNumbers(int id, unsigned long seed)
: id_(id)
, random_(seed)
{}
private:
void threadImpl()
{
cout << id_ << " " << "Start generating random numbers" << endl;
stopwatch_.start();
for(int i = 0; i < 20; ++i)
cout << id_ << " Nr. " << i << ": " << random_.rand() << endl;
stopwatch_.stop();
cout << id_ << " " << "Stop generating" << endl;
cout << id_ << " " << "Time in seconds: " << (double)(stopwatch_.difference()*1.0)/(stopwatch_.frequency()*1.0) << endl;
cout << id_ << " " << "Frequency: " << stopwatch_.frequency() << endl;
cout << id_ << " " << "Timestamp: " << (unsigned) stopwatch_.stamp() << endl;
}
private:
const int id_;
PTime stopwatch_;
PRandom random_;
};
}
void beispiel10()
{
cout << "Generieren von Zufallszahlen in mehreren Threads. Beide Threads generieren\n";
cout << "exakt die selben Zufallszahlen. ";
cout << "Press enter to start" << endl;
cin.get();
using namespace beispiel_10;
const unsigned long seed = (unsigned long)time(0);
GenerateNumbers g1(1, seed), g2(2, seed);
g1.run();
g2.run();
g1.join();
g2.join();
}
namespace beispiel_11
{
class SelfDeleter : public PThread
{
private:
void threadImpl()
{
cout << "Thread: waiting to be unblocked..." << endl;
pt_second_sleep(1);
cout << "Thread: deleting myself now, still joinable!" << endl;
delete this;
}
};
}
void beispiel11()
{
using namespace beispiel_11;
cout << "\n-> Joining on a thread that deletes himself. Press enter to start.\n" << endl;
cin.get();
SelfDeleter* sd = new SelfDeleter();
cout << "Main: starting thread..." << endl;
sd->run();
cout << "Main: joining thread" << endl;
// NOTE: join MUST be called before the thread executes
// his delete this statement. Otherwise a method is
// called on invalid memory!
sd->join();
}
void beispiel12()
{
using namespace beispiel_11;
cout << "\n-> Trying multiple runs on a thread that deletes himself. Press enter to start.\n" << endl;
cin.get();
SelfDeleter* sd = new SelfDeleter();
cout << "Main: starting thread..." << endl;
int ret = sd->run();
assert(ret == 0);
ret = sd->run();
assert(ret < 0);
cout << "Main: joining thread" << endl;
// NOTE: join MUST be called before the thread executes
// his delete this statement. Otherwise a method is
// called on invalid memory!
sd->join();
}
namespace beispiel_13
{
class DeadlockAttempt : public PThread
{
void threadImpl()
{
#ifndef WIN32
cout << "deadlock thread: " << pthread_self() << endl;
#endif
cout << "Thread: Attempting to join myself, effectively trying to deadlock..." << endl;
const int ret = join();
assert(ret == PT_THREAD_DEADLOCK);
cout << "Thread: join failed (good!)" << endl;
}
};
}
void beispiel13()
{
using namespace beispiel_13;
cout << "\n-> No thread can join itself. Press enter to start.\n" << endl;
cin.get();
#ifndef WIN32
cout << "main thread: " << pthread_self() << endl;
#endif
DeadlockAttempt da;
da.run();
int ret = da.join();
assert(ret != PT_THREAD_DEADLOCK);
}
#if 0
void beispiel14()
{
using namespace beispiel_13;
cout << "\n-> Join via dtor\n" << endl;
cin.get();
#ifndef WIN32
cout << "main thread: " << pthread_self() << endl;
#endif
DeadlockAttempt da;
da.run();
}
#endif
int main()
{
#ifdef _MSC_VER
if(1)
{
_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) |
_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF |
_CRTDBG_CHECK_ALWAYS_DF);
_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
_CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
_CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
_CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
_CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR );
}
#endif
beispiel1();
beispiel1_1();
beispiel2();
beispiel3();
beispiel4();
beispiel5();
beispiel6();
beispiel7();
beispiel9();
beispiel10();
beispiel11();
beispiel12();
beispiel13();
printf("main end\n");
return 0;
}