Embedding › 05 Concurrency

Concurrency

Run language code on host threads, choose between one shared VM and several isolated ones, and follow the lifecycle rules that keep the GC happy.

1. Two shapes

One VM, many threadsMany VMs
Heapsharedone per VM
Singletonsshared (the same instance everywhere)one instance per VM
GCone collector, stops all threadsindependent collectors
Use forcomponents that cooperate on the same datacomponents that must not interfere

Both shapes can coexist: an Engine can back one shared VM and several isolated ones.

2. Running two entries in one VM

Each host thread attaches to the VM, runs its entry, and leaves:

typedef struct { VM* vm; ClassMeta* entry; int rc; } Job;

static int run_entry(void* p) {
    Job* j = (Job*)p;
    LothThread* t = loth_engine_thread_attach(j->vm);
    if (!t) { j->rc = 2; return 2; }
    j->rc = vm_run(j->vm, j->entry, 0, NULL);
    loth_engine_thread_leave(j->vm, t);
    return 0;
}

/* in main, after loading AppA and AppB: */
Job ja = { vm, loth_engine_entry(e, "com.example.host.AppA"), -1 };
Job jb = { vm, loth_engine_entry(e, "com.example.host.AppB"), -1 };
void* ta = loth_thread_create(run_entry, &ja, "AppA");
void* tb = loth_thread_create(run_entry, &jb, "AppB");
loth_thread_join((LothThread*)ta);
loth_thread_join((LothThread*)tb);

The example verifies the result: AppA bumps a shared singleton 2000 times, AppB 3000 times, and the host checks that the total is exactly 5000. If each thread had its own singleton, or the increments were not synchronized, that check would fail.

3. Isolated VMs

static int worker(void* p) {
    Job* j = p;
    Arena a;
    arena_init(&a);
    VM* vm = vm_new(&a);
    loth_engine_setup_vm(j->e, vm);
    int rc = vm_run(vm, j->entry, 0, NULL);
    vm_destroy(vm);
    arena_free_all(&a);
    return rc;
}

Each thread owns its VM and its arena, so the threads never touch each other's heap. The Engine's modules are read-only and shared safely. Each VM can also get its own output callback to keep the output of different VMs separate.

4. Threads and roots

5. Lifecycle rules

6. A note on data races

Sharing a heap means sharing the responsibility: unsynchronized access to the same mutable data from two threads is undefined behavior, exactly as in the language track. The synchronization tools (synchronized, volatile, Lock, Cond) are the same ones the language uses; hosts coordinate with them through language methods.