Inhaltsverzeichnis

Nebenläufigkeit

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


Die Bausteine

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).


Beispiel

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.


Warum das ohne Schutz nicht geht

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.


Mutex oder atomar?

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.


Regeln, die Ärger ersparen


Nebenläufigkeit in einer Oberfläche

Eine Vega-Anwendung hat bereits eine Ereignisschleife. Dort gilt:


Fallstricke

Letzte Aktualisierung: 2026-08-19 — API gegen std/thread.lyx erhoben; Beispiel und Wettrennen mit lyxc 1.1.3I gemessen.