zxubian/cozi
Concurrency primitives for Zig
Fibers, thread pools, futures - all in userland Zig. Oh My!
0.15.0-dev.670+1a08c83eb
zig fetch --save git+https://github.com/zxubian/cozi.git#main
cozi
module to your executable:// build.zig
const cozi = b.dependency("cozi", .{});
exe.root_module.addImport("cozi", cozi.module("root"));
You can modify the behavior of cozi
by overriding build options at import timing.
cozi
when registering it as a dependency://build.zig
const exe_mod = b.createModule(.{...});
// import cozi's build script
const cozi_build = @import("./cozi_build.zig");
// register cozi's build options & gather results
const cozi_build_options = cozi_build.parseBuildOptions(b);
// pass the parsed results to the cozi dependency
const cozi = b.dependency("cozi", cozi_build_options);
exe_mod.addImport("cozi", cozi.module("root"));
zig build -h
# > -Dcozi_log=[bool] Enable verbose logging from inside the cozi library
# ...
For each option that is not overridden, cozi
will use the default defined in BuildOptions.
cozi
is experimental and unstable. Expect main
branch to occasionally break.
# get list of available examples
zig build example-run
# build & run specific example
zig build example-run -Dexample-name="some_example"
# build documentation
zig build docs
# host docs on local http server
python3 -m http.server 8000 -d ./zig-out/docs
# open in browser
http://localhost:8000/index.html
Executor
is a type-erased interface representing an abstract task queue:cozi
's concurrency primitives (Future
s, Fiber
s) can run on any Executor
Executor
is to asynchronous task execution what Allocator is to memory management.const executor = thread_pool.executor();
executor.submit(some_function, .{args}, allocator);
// eventually, some_function(args) will be called.
// exact timing depends on the specific Executor implementation
cozi
runtimeSee Coroutine - Supported Platforms
const Ctx = struct {
sum: usize,
wait_group: std.Thread.WaitGroup = .{},
// non-blocking mutex for fibers
mutex: Fiber.Mutex = .{},
pub fn run(
self: *@This(),
) void {
for (0..10) |_| {
{
// Fibers running on thread pool may access
// shared variable `sum` in parallel.
// Fiber.Mutex provides mutual exclusion without
// blocking underlying thread.
self.mutex.lock();
defer self.mutex.unlock();
self.sum += 1;
}
// Suspend execution here (allowing for other fibers to be run),
// and immediately reschedule self with the Executor.
Fiber.yield();
}
self.wait_group.finish();
}
};
var ctx: Ctx = .{ .sum = 0 };
const fiber_count = 4;
ctx.wait_group.startMany(fiber_count);
// Run 4 fibers on 2 threads
for (0..4) |fiber_id| {
try Fiber.goWithNameFmt(
Ctx.run,
.{&ctx},
allocator,
executor,
"Fiber #{}",
.{fiber_id},
);
}
// Synchronize Fibers running in a thread pool
// with the launching (main) thread.
ctx.wait_group.wait();
NOTE
documentation WIP
See issue.
Arch\OS | MacOS | Windows | Linux |
---|---|---|---|
aarch64 | ✅ | ❌ | ❌ |
x86_64 | ❌ | ✅ | ❌ |
const cozi = @import("cozi");
const Coroutine = cozi.Coroutine;
// ...
const Ctx = struct {
pub fn run(ctx: *Coroutine) void {
log.debug("step 1", .{});
ctx.@"suspend"();
log.debug("step 2", .{});
ctx.@"suspend"();
log.debug("step 3", .{});
}
};
var coro: Coroutine.Managed = undefined;
try coro.initInPlace(Ctx.run, .{&coro.coroutine}, gpa.allocator());
defer coro.deinit();
for (0..3) |_| {
coro.@"resume"();
}
assert(coro.isCompleted());
The design of cozi is heavily based on prior work, especially the concurrency course taught by Roman Lipovsky at MIPT. The author would like to express his deepest gratitude to Roman for all of the knowledge that he shares publicly, and for his dedication to education in technology. This library began as a fun exercise to go along with the course, and would not exist without it.
Honourable mentions: