From ab3c4982261e5f3415bb3192b09a73c4321220fb Mon Sep 17 00:00:00 2001 From: Chris Boesch Date: Tue, 14 Apr 2026 11:07:13 +0200 Subject: [PATCH] new example for concurrency --- build.zig | 7 +-- exercises/093_async9.zig | 76 +++++++++++++++++++++++--------- patches/patches/093_async9.patch | 29 +++++++----- 3 files changed, 75 insertions(+), 37 deletions(-) diff --git a/build.zig b/build.zig index eaaefe6..303b76b 100644 --- a/build.zig +++ b/build.zig @@ -1179,12 +1179,7 @@ const exercises = [_]Exercise{ }, .{ .main_file = "093_async9.zig", - .output = - \\Computing concurrently! - \\Main continues... - \\Main done waiting. - \\Result: 123 - , // pay attention to the comma + .output = "Worker 1 found signal start over threshold at index 12!", }, .{ .main_file = "094_async10.zig", diff --git a/exercises/093_async9.zig b/exercises/093_async9.zig index e7d5f68..9d95092 100644 --- a/exercises/093_async9.zig +++ b/exercises/093_async9.zig @@ -30,40 +30,74 @@ // defer _ = future.cancel(io); // const result = future.await(io); // -// Notice the 'try' — that's the key difference in usage! +// Let's try a slightly simplified example from signal processing: +// Suppose we're looking for the beginning of a signal above the noise +// level. To do this, we compare each entry from beginning to end with +// the threshold.To speed things up a bit, we split the signal into +// two halves and have two parallel workers search for them. +// Who finds the beginning first "wins" and thus ends the other one. // -// Fix this program to launch the computation concurrently. +// As I said, this is a simplified explanation, +// but in practice it's done more or less like this. // const std = @import("std"); +const Io = std.Io; const print = std.debug.print; +const SearchResult = struct { + worker_id: u8, + index: usize, +}; + pub fn main(init: std.process.Init) !void { const io = init.io; - // Launch with a guaranteed separate unit of concurrency. - // Which Io method guarantees this? - // (Hint: unlike io.async, this one can fail!) - var future = try io.???(compute, .{io}); - defer _ = future.cancel(io); + const data = [_]u32{ 10, 23, 45, 67, 12, 69, 3, 54, 69, 42, 68, 56, 71, 79, 79, 75, 70, 77 }; + const threshold = 70; + const mid = data.len / 2; - // Note: All breaks in this exercise (using sleep) - // are only necessary for a deterministic result. - io.sleep(std.Io.Duration.fromMilliseconds(100), .awake) catch {}; + // A queue with space for one result. + var buf: [1]SearchResult = undefined; + var queue = Io.Queue(SearchResult).init(&buf); - print("Main continues...\n", .{}); + // Launch two workers, each searching half the array. + var f1 = ???(searchRange, .{ data[0..mid], target, 0, 0, &queue, io }); + defer _ = f1.cancel(io); - // Wait 1 second for the output order. - io.sleep(std.Io.Duration.fromMilliseconds(200), .awake) catch {}; + var f2 = ???(searchRange, .{ data[mid..], target, mid, 1, &queue, io }); + defer _ = f2.cancel(io); - print("Main done waiting.\n", .{}); + // Wait for the first result. + const result = try queue.getOne(io); - const result = future.await(io); - print("Result: {}\n", .{result}); + print("Worker {} found signal start over threshold at index {}!\n", .{ result.worker_id, result.index }); } -fn compute(io: std.Io) u32 { - print("Computing concurrently!\n", .{}); - // Simulate some work. - io.sleep(std.Io.Duration.fromMilliseconds(400), .awake) catch return 0; - return 123; +fn searchThreshold( + io: Io, + slice: []const u32, + threshold: u32, + base_offset: usize, + worker_id: u8, + queue: *Io.Queue(SearchResult), +) void { + for (slice, 0..) |val, i| { + // This pause is necessary so that the process can be canceled + // if another one has already finished. Without this pause, + // all workers would continue until the end. + io.sleep(Io.Duration.fromMilliseconds(1), .awake) catch return; + + // To test this, you can view the work of the workers + // and then comment out the pause. + // print("id: {} - val: {}\n", .{ worker_id, val }); + + if (val >= threshold) { + queue.putOne(io, .{ + .worker_id = worker_id, + .index = base_offset + i, + }) catch return; + return; + } + } } + diff --git a/patches/patches/093_async9.patch b/patches/patches/093_async9.patch index ad81f00..ef18d3f 100644 --- a/patches/patches/093_async9.patch +++ b/patches/patches/093_async9.patch @@ -1,11 +1,20 @@ ---- exercises/093_async9.zig 2026-04-13 17:55:35.567204530 +0200 -+++ answers/093_async9.zig 2026-04-13 18:05:05.636355044 +0200 -@@ -43,7 +43,7 @@ - // Launch with a guaranteed separate unit of concurrency. - // Which Io method guarantees this? - // (Hint: unlike io.async, this one can fail!) -- var future = try io.???(compute, .{io}); -+ var future = try io.concurrent(compute, .{io}); - defer _ = future.cancel(io); +--- exercises/093_async9.zig 2026-04-14 09:50:05.694073287 +0200 ++++ answers/093_async9.zig 2026-04-14 09:49:58.604934765 +0200 +@@ -61,10 +61,10 @@ + var queue = Io.Queue(SearchResult).init(&buf); - // Note: All breaks in this exercise (using sleep) + // Launch two workers, each searching half the array. +- var f1 = ???(searchRange, .{ data[0..mid], target, 0, 0, &queue, io }); ++ var f1 = try io.concurrent(searchThreshold, .{ io, data[0..mid], threshold, 0, 0, &queue }); + defer _ = f1.cancel(io); + +- var f2 = ???(searchRange, .{ data[mid..], target, mid, 1, &queue, io }); ++ var f2 = try io.concurrent(searchThreshold, .{ io, data[mid..], threshold, mid, 1, &queue }); + defer _ = f2.cancel(io); + + // Wait for the first result. +@@ -100,4 +100,3 @@ + } + } + } +-