revival of the async-io functions

This commit is contained in:
Chris Boesch
2026-04-01 23:34:16 +02:00
parent db1fef8b86
commit fcfb0e80a0
5 changed files with 81 additions and 56 deletions

View File

@@ -1131,9 +1131,12 @@ const exercises = [_]Exercise{
}, },
.{ .{
.main_file = "087_async4.zig", .main_file = "087_async4.zig",
.output = "1 2 3 4 5", .output =
.skip = true, \\Task 1 done.
.skip_hint = "async has not been implemented in the current compiler version.", \\Task 2 done.
\\Task 3 done.
\\All tasks finished!
, // pay attention to the comma
}, },
.{ .{
.main_file = "088_async5.zig", .main_file = "088_async5.zig",

View File

@@ -1,30 +1,50 @@
// //
// It has probably not escaped your attention that we are no // When you have many tasks that don't return individual values,
// longer capturing a return value from foo() because the 'async' // use a Group! A Group is an unordered set of tasks that can
// keyword returns the frame instead. // only be awaited or canceled as a whole:
// //
// One way to solve this is to use a global variable. // var group: std.Io.Group = .init;
// group.async(io, myTask, .{arg1});
// group.async(io, myTask, .{arg2});
// try group.await(io); // blocks until ALL tasks finish
// //
// See if you can make this program print "1 2 3 4 5". // Important rules:
// * The return type of functions spawned in a group must be
// coercible to Cancelable!void (i.e. void, or error{Canceled}!void).
// * Once you call group.async(), you MUST eventually call
// group.await() or group.cancel() to release resources.
// * group.cancel() requests cancellation on ALL members,
// then waits for them to finish.
// //
const print = @import("std").debug.print; // Unlike Future, Group tasks don't return values to the caller.
// They're ideal for parallel work that communicates through
// shared state or side effects (like printing).
//
// Fix this program to await all tasks in the group.
//
const std = @import("std");
const print = std.debug.print;
var global_counter: i32 = 0; pub fn main(init: std.process.Init) !void {
const io = init.io;
pub fn main() void { var group: std.Io.Group = .init;
var foo_frame = async foo();
while (global_counter <= 5) { // Spawn 3 tasks in any order. Each sleeps for (id * 1) seconds
print("{} ", .{global_counter}); // before printing, so the output order is deterministic.
??? group.async(io, doWork, .{ io, 1 });
} group.async(io, doWork, .{ io, 3 });
group.async(io, doWork, .{ io, 2 });
print("\n", .{}); // Wait for all tasks to finish.
// What Group method blocks until all tasks complete?
try group.???
print("All tasks finished!\n", .{});
} }
fn foo() void { fn doWork(io: std.Io, id: u32) void {
while (true) { // Sleep ensures deterministic output order.
??? io.sleep(std.Io.Duration.fromSeconds(id), .awake) catch return;
??? print("Task {} done.\n", .{id});
}
} }

View File

@@ -1,31 +1,22 @@
// //
// Whenever there is a lot to calculate, the question arises as to how // In Exercises 84-91, we learned about Zig's Io interface for
// tasks can be carried out simultaneously. We have already learned about // concurrent execution: io.async(), Group, Select, and Futures.
// one possibility, namely asynchronous processes, in Exercises 84-91. // Under the hood, the Threaded backend manages a pool of real
// OS threads for you - including scheduling, cancellation, and
// resource cleanup.
// //
// However, the computing power of the processor is only distributed to // But sometimes you need direct control over threads:
// the started and running tasks, which always reaches its limits when // * Long-lived dedicated workers
// pure computing power is called up. // * Specific stack sizes or thread counts
// * Code that doesn't have an Io interface available
// * Fine-grained synchronization patterns
// //
// For example, in blockchains based on proof of work, the miners have // That's where std.Thread comes in. It gives you a raw OS thread
// to find a nonce for a certain character string so that the first m bits // that you spawn, manage, and join yourself. No pool, no Futures,
// in the hash of the character string and the nonce are zeros. // no automatic cancellation - but full control.
// As the miner who can solve the task first receives the reward, everyone
// tries to complete the calculations as quickly as possible.
// //
// This is where multithreading comes into play, where tasks are actually // The following diagram roughly illustrates the difference between
// distributed across several cores of the CPU or GPU, which then really // the various types of process execution:
// means a multiplication of performance.
//
// The following diagram roughly illustrates the difference between the
// various types of process execution.
// The 'Overall Time' column is intended to illustrate how the time is
// affected if, instead of one core as in synchronous and asynchronous
// processing, a second core now helps to complete the work in multithreading.
//
// In the ideal case shown, execution takes only half the time compared
// to the synchronous single thread. And even asynchronous processing
// is only slightly faster in comparison.
// //
// //
// Synchronous Asynchronous // Synchronous Asynchronous
@@ -108,7 +99,7 @@ pub fn main() !void {
// they run in parallel and we can still do some work in between. // they run in parallel and we can still do some work in between.
var io_instance: std.Io.Threaded = .init_single_threaded; var io_instance: std.Io.Threaded = .init_single_threaded;
const io = io_instance.io(); const io = io_instance.io();
try io.sleep(std.Io.Duration.fromSeconds(4), .awake); try io.sleep(std.Io.Duration.fromMilliseconds(400), .awake);
std.debug.print("Some weird stuff, after starting the threads.\n", .{}); std.debug.print("Some weird stuff, after starting the threads.\n", .{});
} }
// After we have left the closed area, we wait until // After we have left the closed area, we wait until
@@ -118,17 +109,17 @@ pub fn main() !void {
// This function is started with every thread that we set up. // This function is started with every thread that we set up.
// In our example, we pass the number of the thread as a parameter. // In our example, we pass the number of the thread as a parameter.
fn thread_function(num: usize) !void { fn thread_function(id: usize) !void {
var io_instance: std.Io.Threaded = .init_single_threaded; var io_instance: std.Io.Threaded = .init_single_threaded;
const io = io_instance.io(); const io = io_instance.io();
try io.sleep(std.Io.Duration.fromSeconds(1 * @as(isize, @intCast(num))), .awake); try io.sleep(std.Io.Duration.fromMilliseconds(100 * @as(isize, @intCast(id))), .awake);
std.debug.print("thread {d}: {s}\n", .{ num, "started." }); std.debug.print("thread {d}: {s}\n", .{ id, "started." });
// This timer simulates the work of the thread. // This timer simulates the work of the thread.
const work_time = 3 * ((5 - num % 3) - 2); const work_time = 300 * ((5 - id % 3) - 2);
try io.sleep(std.Io.Duration.fromSeconds(@intCast(work_time)), .awake); try io.sleep(std.Io.Duration.fromMilliseconds(@intCast(work_time)), .awake);
std.debug.print("thread {d}: {s}\n", .{ num, "finished." }); std.debug.print("thread {d}: {s}\n", .{ id, "finished." });
} }
// This is the easiest way to run threads in parallel. // This is the easiest way to run threads in parallel.
// In general, however, more management effort is required, // In general, however, more management effort is required,

View File

@@ -0,0 +1,11 @@
--- exercises/087_async4.zig 2026-04-01 23:17:31.066443941 +0200
+++ answers/087_async4.zig 2026-04-01 23:17:39.251612131 +0200
@@ -38,7 +38,7 @@
// Wait for all tasks to finish.
// What Group method blocks until all tasks complete?
- try group.???
+ try group.await(io);
print("All tasks finished!\n", .{});
}

View File

@@ -1,6 +1,6 @@
--- exercises/104_threading.zig 2025-11-28 14:17:31.552529679 +0100 --- exercises/104_threading.zig 2026-04-01 23:31:10.073198955 +0200
+++ answers/104_threading.zig 2025-11-28 14:15:36.823931851 +0100 +++ answers/104_threading.zig 2026-04-01 23:29:51.314585919 +0200
@@ -97,12 +97,12 @@ @@ -88,12 +88,12 @@
defer handle.join(); defer handle.join();
// Second thread // Second thread