A C library that several threads call is thread-safe only as far as its own code makes it so, and its manual says how far, routine by routine. A Mica library is no different, with one difference in who does the work: the library states its shared state, and the compiler keeps it and writes the manual’s words into the header for you. The example ThreadsCallMica is a plain C program whose four POSIX threads call one Mica library at the same time. The C side calls the library’s body entry once, as every C program does, and its threads call nothing first and take no lock.

This page builds on C calls Mica, which shows the archive, the naming rule and the link line. Read it first if you have not linked a Mica library from C before.

The library

The library Turnstile counts people walking through a turnstile. Its state between calls is two unit variables, and every routine that several threads may call at once touches them inside a synchronized statement:

library Turnstile;

var
    passes : int64;
    walkers : int64;

exp procedure Walk(people : int64);
var
    person : int64;
begin
    synchronized walkers := walkers + 1;

    for person := 1 to people do
        synchronized passes := passes + 1;
end;

exp function Passes() : int64;
begin
    synchronized Passes := passes;
end;

exp procedure Reset();
begin
    passes := 0;
    walkers := 0;
end;

begin
end.

A synchronized statement is a critical section the compiler places for you: in a library built for several threads it always takes its lock, so two threads that add at the same moment never lose an addition. Reset touches both counts without one, on purpose, so the header has something to warn about. The example’s file carries one more reader, Walkers, and a comment on every line.

The library is built as an archive, for several threads, with its header written beside it:

mica --compile --link --output-modifier archive --emit-header --optimize O2 \
    --platform linux,arm64,utf-32 --tasking multicore --project mica.project \
    --source Turnstile.mica --build ../build/ThreadsCallMica

The C side

Each thread’s whole work is one call. The call is the thread’s first, and the library makes the thread a Mica task of its own right there, with its own failure record and its own heap partition, and gives the task back when the thread ends:

#include "Turnstile.h"

#include <pthread.h>

static void *walk_group(void *unused)
{
    (void)unused;
    Turnstile__Walk(people_per_thread);
    return NULL;
}

main calls Turnstile__init() once, the library’s body entry, which every C program calls before its first call into a Mica library. It then starts four threads with pthread_create, joins them, and prints what the library counted. The program links the library with the standard library and the multicore runtime floor, mica-dragon-rt-mc-<arch>.a:

cc -std=c23 -pthread -I ../build/ThreadsCallMica -o threadscallmica main.c \
    ../build/ThreadsCallMica/Turnstile.a /usr/lib/mica-stdlib-arm64.a /usr/lib/mica-dragon-rt-mc-arm64.a -lm

The run

make -C mica-container/examples/ThreadsCallMica run

The program prints the same counts on every run, because no addition is lost:

ThreadsCallMica - 4 C threads call one Mica library

The walks
  threads                  = 4
  people per thread        = 100000

What the library counted
  walks                    = 4
  passes                   = 400000 of 400000

After the reset
  passes                   = 0

Four hundred thousand additions from four threads, and every one of them counted. The program calls Reset only after it joined its threads, when no other thread is inside the library.

What the header says

The header --emit-header wrote, Turnstile.h, declares each routine under its linkage name and states its thread safety in the words of the GNU C Library’s manual: MT-Safe where the routine touches no state of the library outside a synchronized statement, and MT-Unsafe with a race: word for each state it touches without one. Above the routines it states the runtime the library was built for.

make -C mica-container/examples/ThreadsCallMica header
 * The library is built for the multicore runtime, on which every C thread that calls it becomes a task of its own on
 * its first call, with its own failure record, result region and heap partition and its own place in the lock of every
 * 'synchronized' statement, so the library's marks keep the threads apart as a C library's own locks would. Several
 * threads may call the library at once, and each routine's comment below states its own thread safety.
/** MT-Safe */
void Turnstile__Walk(int64_t people);
/** MT-Safe */
int64_t Turnstile__Passes(void);
/** MT-Safe */
int64_t Turnstile__Walkers(void);
/** MT-Unsafe race:passes race:walkers */
void Turnstile__Reset(void);

The build says the same where you read it, in a note naming every routine that is MT-Unsafe and the states it races on. Put Reset’s two counts inside a synchronized statement and build again, and its comment reads MT-Safe too. A routine that trains reads race:tape, since a process has one gradient tape.

The same library, built for one thread

Build Turnstile with --tasking single and its header states the runtime it was built for and keeps C’s rule for a library that is not thread-safe:

make -C mica-container/examples/ThreadsCallMica header-single
 * The library is built for the single-carrier runtime, on which every thread that calls it runs as the process's one
 * task: the running task, the marks and the memory regions are one set for the whole process, so two threads inside the
 * library at once would share them. A program that calls the library from several threads makes them take turns,
 * holding one lock of its own around every call.

A program that calls such a library from several threads makes them take turns with a mutex of its own, around every call.

The two builds link two floors, and the link keeps them apart. The multicore archive linked with the floor built for one thread, mica-dragon-rt-<arch>.a, is refused by the C linker, which names the floor flavor the archive needs:

make -C mica-container/examples/ThreadsCallMica link-single-floor
...: hidden symbol `__mica_floor_tasking_multicore' isn't defined

The mirror is refused the same way: the library built for one thread, linked with the multicore floor, names __mica_floor_tasking_single, and a Mica program built with a --tasking other than a library’s is stopped by the compiler before its linker runs. The C boundary states every thread rule of a Mica library called from C.

What stays yours

  • The stack. A Mica call runs on the stack of the thread that makes it, so a thread you create needs room for the library’s deepest call, as C calls Mica says.
  • A thread ends outside a call. Let a thread return from the library before it calls pthread_exit or is cancelled: a thread that ends inside a Mica call leaves the program undefined, as the GNU C Library’s manual says of a function it marks AC-Unsafe.
  • MT-Unsafe means what glibc means. The compiler does not stop a second thread from calling Reset. The header tells you not to, the way man 3 strtok does.

Try it

mica-container/devkit/mica-provision gcc     # the C side needs a C compiler, installed once
make -C mica-container/examples/ThreadsCallMica run

Change thread_count in main.c to sixteen and run it again: the library counts sixteen walks and 1600000 passes, and the C side still writes nothing for it.

  walks                    = 16
  passes                   = 1600000 of 1600000

Next

Callbacks at the boundary — the other way round: a C library that calls back into your Mica code while your call runs.