diff options
Diffstat (limited to 'exercises/104_threading.zig')
| -rw-r--r-- | exercises/104_threading.zig | 129 |
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. |
