Skip to content

Instantly share code, notes, and snippets.

@ikskuh
Last active October 1, 2019 10:34
Show Gist options
  • Save ikskuh/e732b1a74f60dc0cdbc0e2f0cf8898f6 to your computer and use it in GitHub Desktop.
Save ikskuh/e732b1a74f60dc0cdbc0e2f0cf8898f6 to your computer and use it in GitHub Desktop.
Abusing async/await to create fake threads
const std = @import("std");
/// stores some state for each coroutine
/// as well as a pointer to the frame to be resumed
const CoroutineState = struct {
pub const Self = @This();
frame: anyframe = undefined,
quit: bool = false,
active: bool = false,
/// must be called from within the coroutine
/// as soon as the coroutine will exit.
/// this is best done by using `defer coro.exit();`.
pub fn exit(self: *Self) void {
self.active = false;
}
/// yields the coroutine.
/// will return an error if the coroutine has been quit.
fn yield(coro: *Self) !void {
suspend {
coro.frame = @frame();
}
if (coro.quit)
return error.Cancelled;
}
};
var coroutines = [_]CoroutineState{CoroutineState{}} ** 3;
/// our coroutine
fn loop(coro: *CoroutineState, prefix: []const u8, limit: i32) !void {
defer coro.exit(); // must be called to recognize that the coroutine was stopped
std.debug.warn("{}: startup\n", prefix);
defer std.debug.warn("{}: shutdown\n", prefix);
var repetition: i32 = 0;
while (repetition < limit) : (repetition += 1) {
std.debug.warn("{}: loop {}\n", prefix, repetition);
try coro.yield(); // yield may throw error.Cancelled
}
}
/// starts a new coroutine with the given parameters
fn start_new(prefix: []const u8, limit: i32) !@Frame(loop) {
for (coroutines) |*coro| {
if (coro.active)
continue;
coro.active = true;
coro.quit = false;
coro.frame = undefined;
return async loop(coro, prefix, limit);
}
return error.OutOfMemory;
}
pub fn main() !void {
// this uses result location semantics to
// store our coroutine frames in the main
// function. in a proper implementation,
// it should use an allocator
_ = try start_new("o", 5);
_ = try start_new("x", 10);
_ = try start_new("~", 20);
// schedule our coroutines here 15 times
var rep: u32 = 0;
while (rep < 15) : (rep += 1) {
var any_alive = false;
for (coroutines) |coro| {
// resume all alive coroutines
if (!coro.active)
continue;
any_alive = true;
resume coro.frame;
}
if (!any_alive)
break;
}
// kill all coroutines that are still alive
for (coroutines) |*coro, index| {
if (coro.active) {
coro.quit = true;
resume coro.frame;
}
}
}
o: startup
o: loop 0
x: startup
x: loop 0
~: startup
~: loop 0
o: loop 1
x: loop 1
~: loop 1
o: loop 2
x: loop 2
~: loop 2
o: loop 3
x: loop 3
~: loop 3
o: loop 4
x: loop 4
~: loop 4
o: shutdown
x: loop 5
~: loop 5
x: loop 6
~: loop 6
x: loop 7
~: loop 7
x: loop 8
~: loop 8
x: loop 9
~: loop 9
x: shutdown
~: loop 10
~: loop 11
~: loop 12
~: loop 13
~: loop 14
~: loop 15
~: shutdown
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment