Damit wir über die Programmierung sprechen können, müssen wir zuerst einige Stammfunktionen festlegen. Im wesentlichen geht es um:
Erzeugen von Threads
Ein Thread wird erzeugt, indem man wta_create aufruft. Durch diesen
Aufruf wird ein Thread erzeugt, der asynchron mit dem aktuellen
Thread die spezifizierte Prozedur auszuführen beginnt. wta_create
liefert einen Handle auf den erzeugten Thread. Dieser Handle kann
der Prozedur wta_join als Argument übergeben werden.
Mit wta_join wird
auf das Beenden des betreffenden Threads gewartet.
#include "wthread.h"
int
wta_create (wta_t_thread *new_thread,
void (*start_routine)(),
unsigned wspsize,
unsigned priority,
unsigned slice_count);
/* Als Funktionswert von 'wta_create' erhaelt man:
SUCCESS => Thread erfolgreich erzeugt
NOMEMORY => zuwenig Speicher, kein Thread erzeugt. */
int
wta_join (wta_t_thread awaited_thread);
/* Als Funktionswert von 'wta_join' erhaelt man:
SUCCESS => Thread erfolgreich beendet
NOTHREAD => Angegebener Thread existiert nicht. */
Im nachfolgenden Programm-Fragment werden die Funktionen a(x) und
b(y) parallel abgearbeitet:
#include "wthread.h"
int b(x)
int x;
{
/* Code der Funktion b */
}
void a()
{
/* Code der Funktion a */
}
main()
{
wta_t_thread example_thread;
wta_init(); /* Initialisierung des Threads Package */
wta_create (&example_thread, a, 1024, 10, 1);
p = b(y);
wta_join (example_thread);
}
In der Praxis wird wta_join nicht sehr häufig angewandt. Die meisten
Threads bleiben während der gesamten Lebensdauer des Prozesses
erhalten. Falls ein Thread beendet, ohne dass ein wta_join aufgesetzt
wurde, so verschwindet dieser Thread ohne Auswirkungen auf andere Threads.
Wechselseitiger Ausschluss
Die einfachste Interaktion zwischen Threads erfolgt durch die
Verwendung gemeinsamen Speichers (shared memory). Das bedeutet in
höheren Sprachen den Zugriff auf globale Variablen. Um die
richtige Funktionsweise der verschiedenen parallelen Threads beim
Zugriff auf globale Variabeln sicherzustellen, verwendet man
Variabeln vom Datentyp wta_t_mutex, auf denen die Operationen
wta_lock,
wta_try_lock und wta_unlock möglich sind.
Erzeugt resp. Gelöscht werden diese
Variabeln mit den Funktionen
wta_create_mutex und wta_delete_mutex
int
wta_create_mutex (wta_t_mutex *mutex);
/* Als Funktionswert von 'wta_create_mutex' erhaelt man:
SUCCESS => Mutex erfolgreich erzeugt
NOMEMORY => zuwenig Speicher, kein Mutex erzeugt. */
int
wta_delete_mutex (wta_t_mutex *mutex);
/* Als Funktionswert von 'wta_delete_mutex' erhaelt man:
SUCCESS => Mutex geloescht
NOMUTEX => Mutex Zeiger ungueltig
NOACCESS => Mutex ungueltig */
int
wta_lock (wta_t_mutex *mutex);
/* Als Funktionswert von 'wta_lock' erhaelt man:
SUCCESS => Mutex reserviert
NOMUTEX => Mutex Zeiger ungueltig
NOACCESS => Mutex ungueltig */
int
wta_try_lock (wta_t_mutex *mutex);
/* Als Funktionswert von 'wta_try_lock' erhaelt man:
MUT_NOW_LOCKED => Mutex wurde reserviert
MUT_ALREADY_LOCKED => Mutex bereits reserviert
NOMUTEX => Mutex Zeiger ungueltig
NOACCESS => Mutex ungueltig */
int
wta_unlock (wta_t_mutex *mutex);
/* Als Funktionswert von 'wta_unlock' erhaelt man:
SUCCESS => Mutex freigegeben
NOMUTEX => Mutex Zeiger ungueltig
NOACCESS => Mutex ungueltig */
Ein 'mutex' hat zwei Zustände: gesperrt und frei. Die
Stammfunktion wta_lock setzt das 'mutex' in den Zustand gesperrt; die
Funktion wta_unlock setzt das 'mutex' in den Zustand frei. Von
einem Thread, der die Funktion wta_lock erfolgreich ausgeführt hat,
sagt man er halte das 'mutex'. Falls ein anderer Thread versucht
die Operation wta_lock auszuführen auf einem bereits gesperrten
'mutex', so wird dieser Thread blockiert bis das 'mutex' frei ist.
Programmfragment zur Anwendung von wta_lock und wta_unlock:
#include "wthread.h"
typedef struct T_QUEUE {
struct T_QUEUE *flink;
struct T_QUEUE *blink;
} t_queue;
typedef struct T_LIST {
t_queue list_head;
int list_item;
} t_list;
extern insert_in_queue(*t_queue, *t_list);
t_queue list_header;
t_list list_element;
wta_t_mutex mutex;
main()
{
wta_init();
wta_create_mutex(&mutex);
/* Andere Threads koennen erzeugt werden und obige Liste verwenden */
wta_lock (&mutex);
insert_in_queue (&list_header, &list_element);
wta_unlock (&mutex);
}
Im einfachsten Falle ist die 'mutex' Variable wie oben eine
gewöhnliche, globale Variable. Selbstverständlich kann diese auch
Teil einer Datenstruktur sein. Im obigen Beispiel wäre es
sinnvoll, diese im Listenkopf einzubauen.
condition variables
Man kann ein 'mutex' als einen Mechanismus betrachten, der den Ablauf einer Betriebsmittelbenützung steuert. Das Betriebsmittel ist in diesem Falle die 'mutex'-Variable. Die Politik der Ablaufsteuerung lautet in diesem Falle: nur ein Thread aufs Mal. Oft aber ist es nötig, komplexere Ablaufsteuerungen zu erreichen. 'condition variables' beruhen auf den von T. Hoare vorgeschlagenen Monitoren. Sie dienen dazu einen Thread zu blockieren bis ein bestimmtes Ereignis eintrifft.
int
wta_create_condition (wta_t_condit *condition);
/* Als Funktionswert von 'wta_create_condition' erhaelt man:
SUCCESS => Condition erfolgreich erzeugt
NOMEMORY => zuwenig Speicher, keine Condition Variable erzeugt.*/
int
wta_delete_condition (wta_t_condit *condition);
/* Als Funktionswert von 'wta_delete_condition' erhaelt man:
SUCCESS => Condition Variable geloescht
NOCONDITION => Condition Variable Zeiger ungueltig
NOACCESS => Condition Variable ungueltig */
int
wta_wait_condition (
wta_t_condit *condition,
wta_t_mutex *mutex);
/* Als Funktionswert von 'wta_wait_condition' erhaelt man:
SUCCESS => Condition Variable reserviert
NOCONDITION => Condition Variable Zeiger ungueltig
NOACCESS => Condition Variable ungueltig */
int
wta_signal_condition ( wta_t_condit *condition);
/* Als Funktionswert von 'wta_signal_condition' erhaelt man:
SUCCESS => wartender Thread befreit
NOTHREAD => kein Thread wartet auf diese Condition
NOCONDITION => Condition Variable Zeiger ungueltig
NOACCESS => Condition Variable ungueltig */
int
wta_signal_all_condition (wta_t_condition *condition);
/* Als Funktionswert von 'wta_signal_all_condition' erhaelt man:
SUCCESS => wartende(r) Thread(s) befreit
NOTHREAD => kein Thread wartet auf diese Condition
NOCONDITION => Condition Variable Zeiger ungueltig
NOACCESS => Condition Variable ungueltig */
Eine 'condition variable' ist immer mit einem 'mutex', und mit
Daten die durch das 'mutex' geschützt werden, verbunden. Die
wta_wait_condition Operation führt implizit eine wta_unlock
-Operation auf dem
'mutex' aus und blockiert den Thread. Die wta_signal_condition Operation
unternimmt nichts, ausser es gibt einen auf dieser 'condition variable'
blockierten Thread, in diesem Falle wird ein Thread
aufgeweckt (möglicherweise warten mehrere Threads). Die
wta_signal_all_condition Operation arbeitet wie die
wta_signal_condition Operation ausser,
dass alle wartenden Threads aufgeweckt werden. Sobald ein Thread
der mit wta_wait_condition gewartet hat aufgeweckt wird, so
führt er erneut
eine wta_lock Operation auf dem 'mutex' aus. Es ist denkbar, dass
das 'mutex' zu diesem Zeitpunkt nicht frei ist; in diesem Falle
blockiert der Thread bis das 'mutex' verfügbar wird.
Das 'mutex', das zu einer 'condition variable' gehört, schützt die
zugehörigen Daten. Falls ein Thread das Betriebsmittel will, so
führt er eine wta_lock Instruktion auf dem 'mutex' aus und
untersucht die gemeinsamen Daten. Falls das Betriebsmittel
verfügbar ist, fährt er fort, andernfalls blockiert er sich, indem
der wta_wait_condition aufruft. Später, wenn ein anderer Thread das
Betriebsmittel wieder freigibt, weckt er einen allfällig wartenden
mit der wta_signal_condition- oder
wta_signal_all_condition-Funktion.
Das nachfolgende Programm zeigt einen Produzenten-Thread, der Elemente erzeugt und in eine Liste einfügt. Der Konsumenten-Thread liest und verarbeitet diese Elemente.
#include "wthread.h"
typedef struct T_LIST {
struct T_LIST *next;
} t_list;
wta_t_mutex mutex;
wta_t_condition non_empty;
t_list head, topList, newElement;
void
consumer ();
{
for (;;) {
/* Entnehmen eines Items aus der Liste */
wta_lock (&mutex);
/*
Achtung die nachfolgende Anweisung erlaubt 'spurious wakeups'
*/
while (head == NULL) wta_wait_condition (&mutex, &non_empty);
topElement = head;
head = head->next;
wta_unlock (&mutex);
/* Verarbeite den Item */
}
}
void
producer ();
{
for (;;) {
/* Produziere neuen Item */
/* Fuege Item in Liste ein */
wta_lock (&mutex);
newElement->next = head;
head = newElement;
wta_signal_condition (&non_empty);
wta_unlock (&mutex);
}
}
main()
{
wta_t_thread cons_thread, prod_thread;
wta_init();
wta_create_mutex (&mutex);
wta_create_condition (&non_empty);
wta_create(&cons_thread, consumer, 1024, 10, 1);
wta_create(&prod_thread, producer, 1024, 10, 1);
wta_join (prod_thread); /* Warte auf die Beendigung des Produzenten */
}