summaryrefslogtreecommitdiff
path: root/exercises/104_threading.zig
diff options
context:
space:
mode:
authorChris Boesch <chrboesch@noreply.codeberg.org>2026-04-03 13:35:56 +0200
committerChris Boesch <chrboesch@noreply.codeberg.org>2026-04-03 13:35:56 +0200
commite0259f43a726f61da14686de802021fcdb9aacd0 (patch)
tree57359102e5e898289b91ada2d65ca742ec118c8b /exercises/104_threading.zig
parentffde357f303e7459a12cfe4b785ae9e8ef9ebe30 (diff)
Insert space for additional async exercises
Diffstat (limited to 'exercises/104_threading.zig')
-rw-r--r--exercises/104_threading.zig129
1 files changed, 0 insertions, 129 deletions
diff --git a/exercises/104_threading.zig b/exercises/104_threading.zig
deleted file mode 100644
index 3c3fa21..0000000
--- a/exercises/104_threading.zig
+++ /dev/null
@@ -1,129 +0,0 @@
-//
-// In Exercises 84-91, we learned about Zig's Io interface for
-// concurrent execution: io.async(), Group, Select, and Futures.
-// Under the hood, the Threaded backend manages a pool of real
-// OS threads for you - including scheduling, cancellation, and
-// resource cleanup.
-//
-// But sometimes you need direct control over threads:
-// * Long-lived dedicated workers
-// * Specific stack sizes or thread counts
-// * Code that doesn't have an Io interface available
-// * Fine-grained synchronization patterns
-//
-// That's where std.Thread comes in. It gives you a raw OS thread
-// that you spawn, manage, and join yourself. No pool, no Futures,
-// no automatic cancellation - but full control.
-//
-// The following diagram roughly illustrates the difference between
-// the various types of process execution:
-//
-//
-// Synchronous Asynchronous
-// Processing Processing Multithreading
-// ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
-// │ Thread 1 │ │ Thread 1 │ │ Thread 1 │ │ Thread 2 │
-// ├──────────┤ ├──────────┤ ├──────────┤ ├──────────┤ Overall Time
-// └──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┴──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┬───────┬───────┬──
-// ├───┤ ├───┤ ├───┤ ├───┤ │ │ │
-// │ T │ │ T │ │ T │ │ T │ │ │ │
-// │ a │ │ a │ │ a │ │ a │ │ │ │
-// │ s │ │ s │ │ s │ │ s │ │ │ │
-// │ k │ │ k │ │ k │ │ k │ │ │ │
-// │ │ │ │ │ │ │ │ │ │ │
-// │ 1 │ │ 1 │ │ 1 │ │ 3 │ │ │ │
-// └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ │ │ │
-// │ │ │ │ 5 Sec │ │
-// ┌────┴───┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ │ │ │
-// │Blocking│ │ T │ │ T │ │ T │ │ │ │
-// └────┬───┘ │ a │ │ a │ │ a │ │ │ │
-// │ │ s │ │ s │ │ s │ │ 8 Sec │
-// ┌─┴─┐ │ k │ │ k │ │ k │ │ │ │
-// │ T │ │ │ │ │ │ │ │ │ │
-// │ a │ │ 2 │ │ 2 │ │ 4 │ │ │ │
-// │ s │ └─┬─┘ ├───┤ ├───┤ │ │ │
-// │ k │ │ │┼┼┼│ │┼┼┼│ ▼ │ 10 Sec
-// │ │ ┌─┴─┐ └───┴────────┴───┴───────── │ │
-// │ 1 │ │ T │ │ │
-// └─┬─┘ │ a │ │ │
-// │ │ s │ │ │
-// ┌─┴─┐ │ k │ │ │
-// │ T │ │ │ │ │
-// │ a │ │ 1 │ │ │
-// │ s │ ├───┤ │ │
-// │ k │ │┼┼┼│ ▼ │
-// │ │ └───┴──────────────────────────────────────────── │
-// │ 2 │ │
-// ├───┤ │
-// │┼┼┼│ ▼
-// └───┴────────────────────────────────────────────────────────────────
-//
-//
-// The diagram was modeled on the one in a blog in which the differences
-// between asynchronous processing and multithreading are explained in detail:
-// https://blog.devgenius.io/multi-threading-vs-asynchronous-programming-what-is-the-difference-3ebfe1179a5
-//
-// Our exercise is essentially about clarifying the approach in Zig and
-// therefore we try to keep it as simple as possible.
-// Multithreading in itself is already difficult enough. ;-)
-//
-const std = @import("std");
-
-pub fn main() !void {
- // This is where the preparatory work takes place
- // before the parallel processing begins.
- std.debug.print("Starting work...\n", .{});
-
- // These curly brackets are very important, they are necessary
- // to enclose the area where the threads are called.
- // Without these brackets, the program would not wait for the
- // end of the threads and they would continue to run beyond the
- // end of the program.
- {
- // Now we start the first thread, with the number as parameter
- const handle = try std.Thread.spawn(.{}, thread_function, .{1});
-
- // Waits for the thread to complete,
- // then deallocates any resources created on `spawn()`.
- defer handle.join();
-
- // Second thread
- const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right?
- defer handle2.join();
-
- // Third thread
- const handle3 = try std.Thread.spawn(.{}, thread_function, .{3});
- defer ??? // <-- something is missing
-
- // After the threads have been started,
- // they run in parallel and we can still do some work in between.
- var io_instance: std.Io.Threaded = .init_single_threaded;
- const io = io_instance.io();
- try io.sleep(std.Io.Duration.fromMilliseconds(400), .awake);
- std.debug.print("Some weird stuff, after starting the threads.\n", .{});
- }
- // After we have left the closed area, we wait until
- // the threads have run through, if this has not yet been the case.
- std.debug.print("Zig is cool!\n", .{});
-}
-
-// This function is started with every thread that we set up.
-// In our example, we pass the number of the thread as a parameter.
-fn thread_function(id: usize) !void {
- var io_instance: std.Io.Threaded = .init_single_threaded;
- const io = io_instance.io();
- try io.sleep(std.Io.Duration.fromMilliseconds(100 * @as(isize, @intCast(id))), .awake);
- std.debug.print("thread {d}: {s}\n", .{ id, "started." });
-
- // This timer simulates the work of the thread.
- const work_time = 300 * ((5 - id % 3) - 2);
- try io.sleep(std.Io.Duration.fromMilliseconds(@intCast(work_time)), .awake);
-
- std.debug.print("thread {d}: {s}\n", .{ id, "finished." });
-}
-// This is the easiest way to run threads in parallel.
-// In general, however, more management effort is required,
-// e.g. by setting up a pool and allowing the threads to communicate
-// with each other using semaphores.
-//
-// But that's a topic for another exercise.