Threads in Lyx sind dünn: clone plus futex, kein Laufzeitsystem dazwischen. Ein Thread ist eine gewöhnliche Funktion, ein Mutex ein 32-Bit-Wort. Das macht das Verhalten vorhersagbar — und verlangt, dass man weiß, was man tut.
Für Echtzeit- und Embedded-Fragen (Scheduler, Prioritäten, POSIX-AE) siehe RTOS & Embedded Concurrency; hier geht es um den Alltag auf Linux.
→ std.thread · Threads (Sprachreferenz) · Speicher in der Praxis
| Baustein | Funktionen |
|---|---|
| Thread | ThreadCreate(func, arg): Thread, ThreadJoin(t), ThreadIsRunning(t), ThreadSelf() |
| Mutex | MutexNew(), MutexLock(m), MutexUnlock(m), MutexTryLock(m), MutexFree(m) |
| Bedingung | CondNew(), CondWait(c, m), CondSignal©, CondBroadcast©, CondFree© |
| Atomar | AtomicNew(start), AtomicLoad(a), AtomicStore(a, v), AtomicAdd(a, d), AtomicFree(a) |
| Geteilter Speicher | SharedMemCreate(size), SharedMemFree(mem) |
| Warten | Sleep(ms), SleepNs(ns), SleepUntil(clockId, absNs) |
Die Thread-Funktion hat die Form fn(arg: int64): int64 und wird als int64 übergeben: ThreadCreate(Arbeiter as int64, 1).
Zwei Threads erhöhen denselben Zähler — einmal über einen Mutex, einmal atomar:
import std.io;
import std.alloc;
import std.thread;
import std.time;
// Gemeinsamer Zustand: hier liegt der Zaehler, den beide Threads erhoehen
var g_mutex: Mutex;
var g_zaehler: int64;
var g_atomar: Atomic;
// Ein Thread ist eine Funktion mit einem int64-Argument
fn Arbeiter(arg: int64): int64 {
var i: int64 := 0;
while (i < 10000) {
// 1. Weg: Mutex um den kritischen Abschnitt
MutexLock(g_mutex);
g_zaehler := g_zaehler + 1;
MutexUnlock(g_mutex);
// 2. Weg: atomare Addition, ohne Sperre
AtomicAdd(g_atomar, 1);
i := i + 1;
}
return 0;
}
fn main(): int64 {
g_mutex := MutexNew();
g_atomar := AtomicNew(0);
g_zaehler := 0;
var t1: Thread := ThreadCreate(Arbeiter as int64, 1);
var t2: Thread := ThreadCreate(Arbeiter as int64, 2);
// Ohne Join endet main womoeglich vor den Threads
ThreadJoin(t1);
ThreadJoin(t2);
PrintLn(StrConcat("mit Mutex: ", IntToStr(g_zaehler)));
PrintLn(StrConcat("atomar: ", IntToStr(AtomicLoad(g_atomar))));
AtomicFree(g_atomar);
MutexFree(g_mutex);
return 0;
}
mit Mutex: 20000
atomar: 20000
Übersetzt und ausgeführt mit lyxc 1.1.3I.
Dasselbe Programm ohne MutexLock/MutexUnlock, dreimal gestartet:
mit Mutex: 16005
mit Mutex: 15624
mit Mutex: 16876
Erwartet wären 20 000. g_zaehler := g_zaehler + 1 ist drei Schritte — lesen, addieren, schreiben —, und dazwischen darf der andere Thread laufen. Die Ergebnisse sind nicht nur falsch, sie sind jedes Mal anders: Genau daran erkennt man ein Wettrennen.
Der atomare Zähler daneben bleibt in allen Läufen korrekt.
| Fall | Mittel |
|---|---|
| Ein einzelner Zähler, eine Marke | Atomic* — kein Umschalten in den Kern, kein Verklemmen möglich |
| Mehrere Werte, die zusammen stimmig bleiben müssen | Mutex um den ganzen Abschnitt |
| Warten, bis etwas fertig ist | Cond plus Mutex |
| Ergebnis eines Threads abholen | ThreadJoin — er wartet und gibt den Stapel des Kindes frei |
Faustregel: Ein Wert → atomar. Ein Zustand → Mutex.
ThreadJoin für jeden Thread. Ohne Join endet main womöglich vor dem Thread — und ThreadJoin gibt außerdem den Stapel des Kindes frei.MutexUnlock auf jedem Rückgabepfad — ein return mitten im Abschnitt ist die häufigste Verklemmung. defer ist hier der ruhigere Weg.MutexFree, CondFree, AtomicFree.Eine Vega-Anwendung hat bereits eine Ereignisschleife. Dort gilt:
NewTimer(ms) plus app.AddTimer; siehe Vega: Anwendungen aufbauen.Invalidate().Sleep als Synchronisation. Es macht das Wettrennen seltener, nicht unmöglich — und ist damit schlimmer als kein Schutz, weil der Fehler erst in Produktion auftritt.timeout.
Letzte Aktualisierung: 2026-08-19 — API gegen std/thread.lyx erhoben; Beispiel und Wettrennen mit lyxc 1.1.3I gemessen.