Threading

XXML provides comprehensive multithreading support through high-level classes that wrap platform-native threading primitives, with compile-time safety through Sendable and Sharable constraints.

Quick Start

xxml
#import Language::Core;
#import Language::Concurrent;

[ Entrypoint
    {
        // Create and start a thread with a lambda
        Instantiate F(None^)()^ As <work> = [ Lambda [] Returns None^ Parameters () {
            Run Console::printLine(String::Constructor("Hello from thread!"));
            Return None::Constructor();
        }];

        Instantiate Concurrent::Thread^ As <t> = Concurrent::Thread::spawn(work);

        // Wait for thread to complete
        Run t.join();

        Run Console::printLine(String::Constructor("Thread finished!"));
        Exit(0);
    }
]

Thread Safety Constraints

Sendable

Marks types safe to move across thread boundaries:

xxml
@Derive(trait = "Sendable")
[ Class <Message> Final Extends None
    [ Public <>
        Property <id> Types Integer^;
        Property <content> Types String^;
        Constructor = default;
    ]
]

// Message can be safely moved to another thread

Sharable

Marks types safe to share (reference) across threads:

xxml
@Derive(trait = "Sharable")
[ Class <Config> Final Extends None
    [ Public <>
        Property <maxConnections> Types Integer^;

        // Immutable after construction
        Constructor Parameters (Parameter <max> Types Integer^) -> {
            Set maxConnections = max;
        }
    ]
]

Note

Sendable types cannot have reference (&) fields. Sharable types should be immutable after construction.

Thread Class

MethodReturnsDescription
spawn(func)Thread^Create and start a new thread
join()Bool^Wait for thread to complete
detach()Bool^Let thread run independently
sleep(ms)NoneSleep current thread
currentId()Integer^Get current thread ID

Mutex and LockGuard

xxml
Instantiate Concurrent::Mutex^ As <mutex> = Concurrent::Mutex::Constructor();

// Using LockGuard (RAII-style)
{
    Instantiate Concurrent::LockGuard^ As <guard> = Concurrent::LockGuard::Constructor(mutex);

    // Critical section - mutex is held
    // ...

    // Lock automatically released when guard goes out of scope
}

Atomic Operations

xxml
Instantiate Concurrent::Atomic^ As <counter> = Concurrent::Atomic::Constructor();

// Increment atomically
Instantiate Integer^ As <newVal> = counter.increment();

// Add value
Run counter.add(Integer::Constructor(5));

// Compare and swap
Instantiate Bool^ As <success> = counter.compareAndSwap(
    Integer::Constructor(6),   // expected
    Integer::Constructor(10)   // desired
);

Condition Variables

xxml
Instantiate Concurrent::Mutex^ As <mutex> = Concurrent::Mutex::Constructor();
Instantiate Concurrent::ConditionVariable^ As <cond> = Concurrent::ConditionVariable::Constructor();
Instantiate Concurrent::Atomic^ As <dataReady> = Concurrent::Atomic::Constructor();

// Consumer waits for data
Run mutex.lock();
While (dataReady.get().equals(Integer::Constructor(0))) -> {
    Run cond.wait(mutex);
}
Run mutex.unlock();

// Producer signals data ready
Run mutex.lock();
Run dataReady.set(Integer::Constructor(1));
Run cond.signal();
Run mutex.unlock();

Semaphore

xxml
// Pool of 2 resources
Instantiate Concurrent::Semaphore^ As <pool> = Concurrent::Semaphore::Constructor(
    Integer::Constructor(2)
);

// Acquire a resource
Run pool.acquire();
// ... use the resource ...
Run pool.release();

Complete Example: Parallel Counter

parallel-counter.xxml
#import Language::Core;
#import Language::Concurrent;

[ Entrypoint
    {
        Instantiate Concurrent::Atomic^ As <counter> = Concurrent::Atomic::Constructor();

        Instantiate F(None^)()^ As <worker1> = [ Lambda [&counter] Returns None^ Parameters () {
            Run Console::printLine(String::Constructor("Worker 1 starting"));
            Run counter.increment();
            Run counter.increment();
            Run counter.increment();
            Return None::Constructor();
        }];

        Instantiate F(None^)()^ As <worker2> = [ Lambda [&counter] Returns None^ Parameters () {
            Run Console::printLine(String::Constructor("Worker 2 starting"));
            Run counter.increment();
            Run counter.increment();
            Run counter.increment();
            Return None::Constructor();
        }];

        Instantiate Concurrent::Thread^ As <t1> = Concurrent::Thread::spawn(worker1);
        Instantiate Concurrent::Thread^ As <t2> = Concurrent::Thread::spawn(worker2);

        Run t1.join();
        Run t2.join();

        // Final count should be 6
        Run Console::printLine(String::Constructor("Final count: "));
        Run Console::printLine(counter.get().toString());

        Exit(0);
    }
]

Best Practices

  • Use LockGuard: RAII-style locking prevents deadlocks from forgotten unlocks
  • Prefer Atomics: For simple counters, atomics are faster than mutex locks
  • Avoid Deadlocks: Always acquire locks in the same order
  • Loop on Conditions: Always use while loops with condition variables to handle spurious wakeups

Class Reference

ClassDescription
ThreadExecution thread with lambda support
MutexMutual exclusion lock
LockGuardRAII scoped lock
AtomicThread-safe atomic integer
ConditionVariableThread coordination/signaling
SemaphoreCounting semaphore
ThreadLocal<T>Per-thread storage

Next Steps

Learn about Derives for Sendable and Sharable constraints, or explore Lambdas for creating thread functions.