mirror of
https://codeberg.org/andyscott/ziglings.git
synced 2024-12-22 06:03:09 -05:00
Compare commits
24 commits
1a1b7937f6
...
4941158639
Author | SHA1 | Date | |
---|---|---|---|
4941158639 | |||
0b00b44e51 | |||
|
a3de4e3d0f | ||
|
0d1c76a410 | ||
|
8e6612a59d | ||
|
95cfeaa606 | ||
|
dabd9a5a0a | ||
|
d65e3f3f9a | ||
|
cdaa246131 | ||
|
2b9e3da5c8 | ||
|
2092f35127 | ||
|
abed92c05e | ||
|
5728ccc8eb | ||
|
a87e7c895e | ||
|
9844123dd1 | ||
|
c8f081f3e8 | ||
|
e3877321b6 | ||
|
08cc60ba59 | ||
|
6b46b26c22 | ||
|
d0519d18fa | ||
|
9e9cf40453 | ||
|
6984345d0a | ||
|
277304454a | ||
|
9e48c9a339 |
20 changed files with 364 additions and 56 deletions
|
@ -1,2 +1,2 @@
|
||||||
# Ziglings
|
# Ziglings
|
||||||
# ⚠️ (My solutions, not the [original exercises](https://codeberg.org/ziglings/exercises))
|
# ⚠️ My solutions, not the [original exercises](https://codeberg.org/ziglings/exercises)
|
||||||
|
|
30
build.zig
30
build.zig
|
@ -15,7 +15,7 @@ const print = std.debug.print;
|
||||||
// 1) Getting Started
|
// 1) Getting Started
|
||||||
// 2) Version Changes
|
// 2) Version Changes
|
||||||
comptime {
|
comptime {
|
||||||
const required_zig = "0.12.0-dev.2618";
|
const required_zig = "0.12.0-dev.3397";
|
||||||
const current_zig = builtin.zig_version;
|
const current_zig = builtin.zig_version;
|
||||||
const min_zig = std.SemanticVersion.parse(required_zig) catch unreachable;
|
const min_zig = std.SemanticVersion.parse(required_zig) catch unreachable;
|
||||||
if (current_zig.order(min_zig) == .lt) {
|
if (current_zig.order(min_zig) == .lt) {
|
||||||
|
@ -119,7 +119,7 @@ pub const logo =
|
||||||
;
|
;
|
||||||
|
|
||||||
pub fn build(b: *Build) !void {
|
pub fn build(b: *Build) !void {
|
||||||
if (!validate_exercises()) std.os.exit(2);
|
if (!validate_exercises()) std.process.exit(2);
|
||||||
|
|
||||||
use_color_escapes = false;
|
use_color_escapes = false;
|
||||||
if (std.io.getStdErr().supportsAnsiEscapeCodes()) {
|
if (std.io.getStdErr().supportsAnsiEscapeCodes()) {
|
||||||
|
@ -172,7 +172,7 @@ pub fn build(b: *Build) !void {
|
||||||
// Named build mode: verifies a single exercise.
|
// Named build mode: verifies a single exercise.
|
||||||
if (n == 0 or n > exercises.len - 1) {
|
if (n == 0 or n > exercises.len - 1) {
|
||||||
print("unknown exercise number: {}\n", .{n});
|
print("unknown exercise number: {}\n", .{n});
|
||||||
std.os.exit(2);
|
std.process.exit(2);
|
||||||
}
|
}
|
||||||
const ex = exercises[n - 1];
|
const ex = exercises[n - 1];
|
||||||
|
|
||||||
|
@ -262,7 +262,7 @@ const ZiglingStep = struct {
|
||||||
print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text });
|
print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text });
|
||||||
|
|
||||||
self.help();
|
self.help();
|
||||||
std.os.exit(2);
|
std.process.exit(2);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.run(exe_path.?, prog_node) catch {
|
self.run(exe_path.?, prog_node) catch {
|
||||||
|
@ -272,7 +272,7 @@ const ZiglingStep = struct {
|
||||||
print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text });
|
print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text });
|
||||||
|
|
||||||
self.help();
|
self.help();
|
||||||
std.os.exit(2);
|
std.process.exit(2);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Print possible warning/debug messages.
|
// Print possible warning/debug messages.
|
||||||
|
@ -939,7 +939,7 @@ const exercises = [_]Exercise{
|
||||||
.{
|
.{
|
||||||
.main_file = "082_anonymous_structs3.zig",
|
.main_file = "082_anonymous_structs3.zig",
|
||||||
.output =
|
.output =
|
||||||
\\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.14159202e+00
|
\\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592e0
|
||||||
,
|
,
|
||||||
.hint = "This one is a challenge! But you have everything you need.",
|
.hint = "This one is a challenge! But you have everything you need.",
|
||||||
},
|
},
|
||||||
|
@ -1103,6 +1103,24 @@ const exercises = [_]Exercise{
|
||||||
\\This little poem has 15 words!
|
\\This little poem has 15 words!
|
||||||
,
|
,
|
||||||
},
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "104_threading.zig",
|
||||||
|
.output =
|
||||||
|
\\Starting work...
|
||||||
|
\\thread 1: started.
|
||||||
|
\\thread 2: started.
|
||||||
|
\\thread 3: started.
|
||||||
|
\\Some weird stuff, after starting the threads.
|
||||||
|
\\thread 2: finished.
|
||||||
|
\\thread 1: finished.
|
||||||
|
\\thread 3: finished.
|
||||||
|
\\Zig is cool!
|
||||||
|
,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "105_threading2.zig",
|
||||||
|
.output = "PI ≈ 3.14159265",
|
||||||
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "999_the_end.zig",
|
.main_file = "999_the_end.zig",
|
||||||
.output =
|
.output =
|
||||||
|
|
|
@ -88,7 +88,7 @@ pub fn main() void {
|
||||||
for (&aliens) |*alien| {
|
for (&aliens) |*alien| {
|
||||||
|
|
||||||
// *** Zap the alien with the heat ray here! ***
|
// *** Zap the alien with the heat ray here! ***
|
||||||
???.zap(???);
|
heat_ray.zap(alien);
|
||||||
|
|
||||||
// If the alien's health is still above 0, it's still alive.
|
// If the alien's health is still above 0, it's still alive.
|
||||||
if (alien.health > 0) aliens_alive += 1;
|
if (alien.health > 0) aliens_alive += 1;
|
||||||
|
|
|
@ -54,7 +54,7 @@ fn visitElephants(first_elephant: *Elephant) void {
|
||||||
|
|
||||||
// This gets the next elephant or stops:
|
// This gets the next elephant or stops:
|
||||||
// which method do we want here?
|
// which method do we want here?
|
||||||
e = if (e.hasTail()) e.??? else break;
|
e = if (e.hasTail()) e.getTail() else break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,13 @@ const Elephant = struct {
|
||||||
// Your Elephant trunk methods go here!
|
// Your Elephant trunk methods go here!
|
||||||
// ---------------------------------------------------
|
// ---------------------------------------------------
|
||||||
|
|
||||||
???
|
pub fn getTrunk(self: *Elephant) *Elephant {
|
||||||
|
return self.trunk.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hasTrunk(self: *Elephant) bool {
|
||||||
|
return (self.trunk != null);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------
|
// ---------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -65,10 +65,10 @@ const std = @import("std");
|
||||||
const Err = error{Cthulhu};
|
const Err = error{Cthulhu};
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
var first_line1: *const [16]u8 = ???;
|
var first_line1: *const [16]u8 = undefined;
|
||||||
first_line1 = "That is not dead";
|
first_line1 = "That is not dead";
|
||||||
|
|
||||||
var first_line2: Err!*const [21]u8 = ???;
|
var first_line2: Err!*const [21]u8 = undefined;
|
||||||
first_line2 = "which can eternal lie";
|
first_line2 = "which can eternal lie";
|
||||||
|
|
||||||
// Note we need the "{!s}" format for the error union string.
|
// Note we need the "{!s}" format for the error union string.
|
||||||
|
@ -77,8 +77,8 @@ pub fn main() void {
|
||||||
printSecondLine();
|
printSecondLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn printSecondLine() ??? {
|
fn printSecondLine() void {
|
||||||
var second_line2: ?*const [18]u8 = ???;
|
var second_line2: ?*const [18]u8 = null;
|
||||||
second_line2 = "even death may die";
|
second_line2 = "even death may die";
|
||||||
|
|
||||||
std.debug.print("And with strange aeons {s}.\n", .{second_line2.?});
|
std.debug.print("And with strange aeons {s}.\n", .{second_line2.?});
|
||||||
|
|
|
@ -87,7 +87,7 @@ pub fn main() void {
|
||||||
// Let's assign the std.debug.print function to a const named
|
// Let's assign the std.debug.print function to a const named
|
||||||
// "print" so that we can use this new name later!
|
// "print" so that we can use this new name later!
|
||||||
|
|
||||||
const print = ???;
|
const print = std.debug.print;
|
||||||
|
|
||||||
// Now let's look at assigning and pointing to values in Zig.
|
// Now let's look at assigning and pointing to values in Zig.
|
||||||
//
|
//
|
||||||
|
@ -141,9 +141,20 @@ pub fn main() void {
|
||||||
//
|
//
|
||||||
// Moving along...
|
// Moving along...
|
||||||
//
|
//
|
||||||
// Passing arguments to functions is pretty much exactly like
|
// When arguments are passed to a function,
|
||||||
// making an assignment to a const (since Zig enforces that ALL
|
// they are ALWAYS passed as constants within the function,
|
||||||
// function parameters are const).
|
// regardless of how they were declared in the calling function.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// fn foo(arg: u8) void {
|
||||||
|
// arg = 42; // Error, 'arg' is const!
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn bar() void {
|
||||||
|
// var arg: u8 = 12;
|
||||||
|
// foo(arg);
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
//
|
//
|
||||||
// Knowing this, see if you can make levelUp() work as expected -
|
// Knowing this, see if you can make levelUp() work as expected -
|
||||||
// it should add the specified amount to the supplied character's
|
// it should add the specified amount to the supplied character's
|
||||||
|
@ -152,13 +163,13 @@ pub fn main() void {
|
||||||
print("XP before:{}, ", .{glorp.experience});
|
print("XP before:{}, ", .{glorp.experience});
|
||||||
|
|
||||||
// Fix 1 of 2 goes here:
|
// Fix 1 of 2 goes here:
|
||||||
levelUp(glorp, reward_xp);
|
levelUp(&glorp, reward_xp);
|
||||||
|
|
||||||
print("after:{}.\n", .{glorp.experience});
|
print("after:{}.\n", .{glorp.experience});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix 2 of 2 goes here:
|
// Fix 2 of 2 goes here:
|
||||||
fn levelUp(character_access: Character, xp: u32) void {
|
fn levelUp(character_access: *Character, xp: u32) void {
|
||||||
character_access.experience += xp;
|
character_access.experience += xp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,8 @@ pub fn main() void {
|
||||||
var cards = [8]u8{ 'A', '4', 'K', '8', '5', '2', 'Q', 'J' };
|
var cards = [8]u8{ 'A', '4', 'K', '8', '5', '2', 'Q', 'J' };
|
||||||
|
|
||||||
// Please put the first 4 cards in hand1 and the rest in hand2.
|
// Please put the first 4 cards in hand1 and the rest in hand2.
|
||||||
const hand1: []u8 = cards[???];
|
const hand1: []u8 = cards[0..4];
|
||||||
const hand2: []u8 = cards[???];
|
const hand2: []u8 = cards[4..8];
|
||||||
|
|
||||||
std.debug.print("Hand1: ", .{});
|
std.debug.print("Hand1: ", .{});
|
||||||
printHand(hand1);
|
printHand(hand1);
|
||||||
|
@ -43,7 +43,7 @@ pub fn main() void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Please lend this function a hand. A u8 slice hand, that is.
|
// Please lend this function a hand. A u8 slice hand, that is.
|
||||||
fn printHand(hand: ???) void {
|
fn printHand(hand: []u8) void {
|
||||||
for (hand) |h| {
|
for (hand) |h| {
|
||||||
std.debug.print("{u} ", .{h});
|
std.debug.print("{u} ", .{h});
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,19 +17,19 @@ const std = @import("std");
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
const scrambled = "great base for all your justice are belong to us";
|
const scrambled = "great base for all your justice are belong to us";
|
||||||
|
|
||||||
const base1: []u8 = scrambled[15..23];
|
const base1: []const u8 = scrambled[15..23];
|
||||||
const base2: []u8 = scrambled[6..10];
|
const base2: []const u8 = scrambled[6..10];
|
||||||
const base3: []u8 = scrambled[32..];
|
const base3: []const u8 = scrambled[32..];
|
||||||
printPhrase(base1, base2, base3);
|
printPhrase(base1, base2, base3);
|
||||||
|
|
||||||
const justice1: []u8 = scrambled[11..14];
|
const justice1: []const u8 = scrambled[11..14];
|
||||||
const justice2: []u8 = scrambled[0..5];
|
const justice2: []const u8 = scrambled[0..5];
|
||||||
const justice3: []u8 = scrambled[24..31];
|
const justice3: []const u8 = scrambled[24..31];
|
||||||
printPhrase(justice1, justice2, justice3);
|
printPhrase(justice1, justice2, justice3);
|
||||||
|
|
||||||
std.debug.print("\n", .{});
|
std.debug.print("\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn printPhrase(part1: []u8, part2: []u8, part3: []u8) void {
|
fn printPhrase(part1: []const u8, part2: []const u8, part3: []const u8) void {
|
||||||
std.debug.print("'{s} {s} {s}.' ", .{ part1, part2, part3 });
|
std.debug.print("'{s} {s} {s}.' ", .{ part1, part2, part3 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub fn main() void {
|
||||||
// we can CONVERT IT TO A SLICE. (Hint: we do know the length!)
|
// we can CONVERT IT TO A SLICE. (Hint: we do know the length!)
|
||||||
//
|
//
|
||||||
// Please fix this line so the print statement below can print it:
|
// Please fix this line so the print statement below can print it:
|
||||||
const zen12_string: []const u8 = zen_manyptr;
|
const zen12_string: []const u8 = zen_manyptr[0..21];
|
||||||
|
|
||||||
// Here's the moment of truth!
|
// Here's the moment of truth!
|
||||||
std.debug.print("{s}\n", .{zen12_string});
|
std.debug.print("{s}\n", .{zen12_string});
|
||||||
|
|
|
@ -59,8 +59,8 @@ pub fn main() void {
|
||||||
std.debug.print("Insect report! ", .{});
|
std.debug.print("Insect report! ", .{});
|
||||||
|
|
||||||
// Oops! We've made a mistake here.
|
// Oops! We've made a mistake here.
|
||||||
printInsect(ant, AntOrBee.c);
|
printInsect(ant, AntOrBee.a);
|
||||||
printInsect(bee, AntOrBee.c);
|
printInsect(bee, AntOrBee.b);
|
||||||
|
|
||||||
std.debug.print("\n", .{});
|
std.debug.print("\n", .{});
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,14 +44,14 @@ pub fn main() void {
|
||||||
std.debug.print("Insect report! ", .{});
|
std.debug.print("Insect report! ", .{});
|
||||||
|
|
||||||
// Could it really be as simple as just passing the union?
|
// Could it really be as simple as just passing the union?
|
||||||
printInsect(???);
|
printInsect(ant);
|
||||||
printInsect(???);
|
printInsect(bee);
|
||||||
|
|
||||||
std.debug.print("\n", .{});
|
std.debug.print("\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn printInsect(insect: Insect) void {
|
fn printInsect(insect: Insect) void {
|
||||||
switch (???) {
|
switch (insect) {
|
||||||
.still_alive => |a| std.debug.print("Ant alive is: {}. ", .{a}),
|
.still_alive => |a| std.debug.print("Ant alive is: {}. ", .{a}),
|
||||||
.flowers_visited => |f| std.debug.print("Bee visited {} flowers. ", .{f}),
|
.flowers_visited => |f| std.debug.print("Bee visited {} flowers. ", .{f}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
// Zig lets you express integer literals in several convenient
|
// Zig lets you express integer literals in several convenient
|
||||||
// formats. These are all the same value:
|
// formats. These are all the same value:
|
||||||
//
|
//
|
||||||
// const a1: u8 = 65; // decimal
|
// const a1: u8 = 65; // decimal
|
||||||
// const a2: u8 = 0x41; // hexadecimal
|
// const a2: u8 = 0x41; // hexadecimal
|
||||||
// const a3: u8 = 0o101; // octal
|
// const a3: u8 = 0o101; // octal
|
||||||
// const a4: u8 = 0b1000001; // binary
|
// const a4: u8 = 0b1000001; // binary
|
||||||
// const a5: u8 = 'A'; // ASCII code point literal
|
// const a5: u8 = 'A'; // ASCII code point literal
|
||||||
// const a6: u16 = 'Ȁ'; // Unicode code points can take up to 21 bits
|
// const a6: u16 = '\u{0041}'; // Unicode code points can take up to 21 bits
|
||||||
//
|
//
|
||||||
// You can also place underscores in numbers to aid readability:
|
// You can also place underscores in numbers to aid readability:
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
//
|
//
|
||||||
// Often, C functions are used where no equivalent Zig function exists
|
// Often, C functions are used where no equivalent Zig function exists
|
||||||
// yet. Since the integration of a C function is very simple, as already
|
// yet. Okay, that's getting less and less. ;-)
|
||||||
|
//
|
||||||
|
// Since the integration of a C function is very simple, as already
|
||||||
// seen in the last exercise, it naturally offers itself to use the
|
// seen in the last exercise, it naturally offers itself to use the
|
||||||
// very large variety of C functions for our own programs.
|
// very large variety of C functions for our own programs.
|
||||||
// As an example:
|
// As an example:
|
||||||
//
|
//
|
||||||
// Let's say we have a given angle of 765.2 degrees. If we want to
|
// Let's say we have a given angle of 765.2 degrees. If we want to
|
||||||
// normalize that, it means that we have to subtract X * 360 degrees
|
// normalize that, it means that we have to subtract X * 360 degrees
|
||||||
// to get the correct angle. How could we do that? A good method is
|
// to get the correct angle.
|
||||||
// to use the modulo function. But if we write "765.2 % 360", it won't
|
// How could we do that? A good method is to use the modulo function.
|
||||||
// work, because the standard modulo function works only with integer
|
// But if we write "765.2 % 360", it only works with float values
|
||||||
// values. In the C library "math", there is a function called "fmod";
|
// that are known at compile time.
|
||||||
// the "f" stands for floating and means that we can solve modulo for
|
// In Zig, we would use %mod(a, b) instead.
|
||||||
// real numbers. With this function, it should be possible to normalize
|
//
|
||||||
// our angle. Let's go.
|
// Let us now assume that we cannot do this in Zig, but only with
|
||||||
|
// a C function from the standard library. In the library "math",
|
||||||
|
// there is a function called "fmod"; the "f" stands for floating
|
||||||
|
// and means that we can solve modulo for real numbers. With this
|
||||||
|
// function, it should be possible to normalize our angle.
|
||||||
|
// Let's go.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
|
129
exercises/104_threading.zig
Normal file
129
exercises/104_threading.zig
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
//
|
||||||
|
// Whenever there is a lot to calculate, the question arises as to how
|
||||||
|
// tasks can be carried out simultaneously. We have already learned about
|
||||||
|
// one possibility, namely asynchronous processes, in Exercises 84-91.
|
||||||
|
//
|
||||||
|
// However, the computing power of the processor is only distributed to
|
||||||
|
// the started tasks, which always reaches its limits when pure computing
|
||||||
|
// power is called up.
|
||||||
|
//
|
||||||
|
// For example, in blockchains based on proof of work, the miners have
|
||||||
|
// to find a nonce for a certain character string so that the first m bits
|
||||||
|
// in the hash of the character string and the nonce are zeros.
|
||||||
|
// 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
|
||||||
|
// distributed across several cores of the CPU or GPU, which then really
|
||||||
|
// 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
|
||||||
|
// 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.
|
||||||
|
std.time.sleep((1) * std.time.ns_per_s);
|
||||||
|
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(num: usize) !void {
|
||||||
|
std.debug.print("thread {d}: {s}\n", .{ num, "started." });
|
||||||
|
std.time.sleep((5 - num % 3) * std.time.ns_per_s);
|
||||||
|
std.debug.print("thread {d}: {s}\n", .{ num, "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.
|
107
exercises/105_threading2.zig
Normal file
107
exercises/105_threading2.zig
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
//
|
||||||
|
// Now that we are familiar with the principles of multi threading, we
|
||||||
|
// boldly venture into a practical example from mathematics.
|
||||||
|
// We will determine the circle number PI with sufficient accuracy.
|
||||||
|
//
|
||||||
|
// There are different methods for this, and some of them are several
|
||||||
|
// hundred years old. For us, the dusty procedures are surprisingly well
|
||||||
|
// suited to our exercise. Because the mathematicians of the time didn't
|
||||||
|
// have fancy computers with which we can calculate something like this
|
||||||
|
// in seconds today.
|
||||||
|
// Whereby, of course, it depends on the accuracy, i.e. how many digits
|
||||||
|
// after the decimal point we are interested in.
|
||||||
|
// But these old procedures can still be tackled with paper and pencil,
|
||||||
|
// which is why they are easier for us to understand.
|
||||||
|
// At least for me. ;-)
|
||||||
|
//
|
||||||
|
// So let's take a mental leap back a few years.
|
||||||
|
// Around 1672 (if you want to know and read about it in detail, you can
|
||||||
|
// do so on Wikipedia, for example), various mathematicians once again
|
||||||
|
// discovered a method of approaching the circle number PI.
|
||||||
|
// There were the Scottish mathematician Gregory and the German
|
||||||
|
// mathematician Leibniz, and even a few hundred years earlier the Indian
|
||||||
|
// mathematician Madhava. All of them independently developed the same
|
||||||
|
// formula, which was published by Leibnitz in 1682 in the journal
|
||||||
|
// "Acta Eruditorum".
|
||||||
|
// This is why this method has become known as the "Leibnitz series",
|
||||||
|
// although the other names are also often used today.
|
||||||
|
// We will not go into the formula and its derivation in detail, but
|
||||||
|
// will deal with the series straight away:
|
||||||
|
//
|
||||||
|
// 4 4 4 4 4
|
||||||
|
// PI = --- - --- + --- - --- + --- ...
|
||||||
|
// 1 3 5 7 9
|
||||||
|
//
|
||||||
|
// As you can clearly see, the series starts with the whole number 4 and
|
||||||
|
// approaches the circle number by subtracting and adding smaller and
|
||||||
|
// smaller parts of 4. Pretty much everyone has learned PI = 3.14 at school,
|
||||||
|
// but very few people remember other digits, and this is rarely necessary
|
||||||
|
// in practice. Because either you don't need the precision, or you use a
|
||||||
|
// calculator in which the number is stored as a very precise constant.
|
||||||
|
// But at some point this constant was calculated and we are doing the same
|
||||||
|
// now.The question at this point is, how many partial values do we have
|
||||||
|
// to calculate for which accuracy?
|
||||||
|
//
|
||||||
|
// The answer is chewing, to get 8 digits after the decimal point we need
|
||||||
|
// 1,000,000,000 partial values. And for each additional digit we have to
|
||||||
|
// add a zero.
|
||||||
|
// Even fast computers - and I mean really fast computers - get a bit warmer
|
||||||
|
// on the CPU when it comes to really many diggits. But the 8 digits are
|
||||||
|
// enough for us for now, because we want to understand the principle and
|
||||||
|
// nothing more, right?
|
||||||
|
//
|
||||||
|
// As we have already discovered, the Leibnitz series is a series with a
|
||||||
|
// fixed distance of 2 between the individual partial values. This makes
|
||||||
|
// it easy to apply a simple loop to it, because if we start with n = 1
|
||||||
|
// (which is not necessarily useful now) we always have to add 2 in each
|
||||||
|
// round.
|
||||||
|
// But wait! The partial values are alternately added and subtracted.
|
||||||
|
// This could also be achieved with one loop, but not very elegantly.
|
||||||
|
// It also makes sense to split this between two CPUs, one calculates
|
||||||
|
// the positive values and the other the negative values. And so we can
|
||||||
|
// simply start two threads and add everything up at the end and we're
|
||||||
|
// done.
|
||||||
|
// We just have to remember that if only the positive or negative values
|
||||||
|
// are calculated, the distances are twice as large, i.e. 4.
|
||||||
|
//
|
||||||
|
// So that the whole thing has a real learning effect, the first thread
|
||||||
|
// call is specified and you have to make the second.
|
||||||
|
// But don't worry, it will work out. :-)
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
const count = 1_000_000_000;
|
||||||
|
var pi_plus: f64 = 0;
|
||||||
|
var pi_minus: f64 = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
// First thread to calculate the plus numbers.
|
||||||
|
const handle1 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_plus, 5, count });
|
||||||
|
defer handle1.join();
|
||||||
|
|
||||||
|
// Second thread to calculate the minus numbers.
|
||||||
|
???
|
||||||
|
|
||||||
|
}
|
||||||
|
// Here we add up the results.
|
||||||
|
std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thread_pi(pi: *f64, begin: u64, end: u64) !void {
|
||||||
|
var n: u64 = begin;
|
||||||
|
while (n < end) : (n += 4) {
|
||||||
|
pi.* += 4 / @as(f64, @floatFromInt(n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If you wish, you can increase the number of loop passes, which
|
||||||
|
// improves the number of digits.
|
||||||
|
//
|
||||||
|
// But be careful:
|
||||||
|
// In order for parallel processing to really show its strengths,
|
||||||
|
// the compiler must be given the "-O ReleaseFast" flag when it
|
||||||
|
// is created. Otherwise the debug functions slow down the speed
|
||||||
|
// to such an extent that seconds become minutes during execution.
|
||||||
|
//
|
||||||
|
// And you should remove the formatting restriction in "print",
|
||||||
|
// otherwise you will not be able to see the additional diggits.
|
|
@ -1,5 +1,5 @@
|
||||||
--- exercises/051_values.zig 2023-10-03 22:15:22.122241138 +0200
|
--- exercises/051_values.zig 2024-03-14 23:25:42.695020607 +0100
|
||||||
+++ answers/051_values.zig 2023-10-05 20:04:07.072767194 +0200
|
+++ answers/051_values.zig 2024-03-14 23:28:34.525109174 +0100
|
||||||
@@ -87,7 +87,7 @@
|
@@ -87,7 +87,7 @@
|
||||||
// Let's assign the std.debug.print function to a const named
|
// Let's assign the std.debug.print function to a const named
|
||||||
// "print" so that we can use this new name later!
|
// "print" so that we can use this new name later!
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
// Now let's look at assigning and pointing to values in Zig.
|
// Now let's look at assigning and pointing to values in Zig.
|
||||||
//
|
//
|
||||||
@@ -152,13 +152,13 @@
|
@@ -163,13 +163,13 @@
|
||||||
print("XP before:{}, ", .{glorp.experience});
|
print("XP before:{}, ", .{glorp.experience});
|
||||||
|
|
||||||
// Fix 1 of 2 goes here:
|
// Fix 1 of 2 goes here:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
--- exercises/094_c_math.zig 2023-10-22 14:00:02.909379696 +0200
|
--- exercises/094_c_math.zig 2024-02-28 12:50:35.789939935 +0100
|
||||||
+++ answers/094_c_math.zig 2023-10-22 14:02:46.709025235 +0200
|
+++ answers/094_c_math.zig 2024-02-28 12:53:57.910309471 +0100
|
||||||
@@ -19,7 +19,7 @@
|
@@ -26,7 +26,7 @@
|
||||||
|
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
// What do we need here?
|
// What do we need here?
|
||||||
|
|
17
patches/patches/104_threading.patch
Normal file
17
patches/patches/104_threading.patch
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
--- exercises/104_threading.zig 2024-03-05 09:09:04.013974229 +0100
|
||||||
|
+++ answers/104_threading.zig 2024-03-05 09:12:03.987162883 +0100
|
||||||
|
@@ -97,12 +97,12 @@
|
||||||
|
defer handle.join();
|
||||||
|
|
||||||
|
// Second thread
|
||||||
|
- const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right?
|
||||||
|
+ const handle2 = try std.Thread.spawn(.{}, thread_function, .{2});
|
||||||
|
defer handle2.join();
|
||||||
|
|
||||||
|
// Third thread
|
||||||
|
const handle3 = try std.Thread.spawn(.{}, thread_function, .{3});
|
||||||
|
- defer ??? // <-- something is missing
|
||||||
|
+ defer handle3.join();
|
||||||
|
|
||||||
|
// After the threads have been started,
|
||||||
|
// they run in parallel and we can still do some work in between.
|
13
patches/patches/105_threading2.patch
Normal file
13
patches/patches/105_threading2.patch
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
--- exercises/105_threading2.zig 2024-03-23 16:35:14.754540802 +0100
|
||||||
|
+++ answers/105_threading2.zig 2024-03-23 16:38:00.577539733 +0100
|
||||||
|
@@ -81,8 +81,8 @@
|
||||||
|
defer handle1.join();
|
||||||
|
|
||||||
|
// Second thread to calculate the minus numbers.
|
||||||
|
- ???
|
||||||
|
-
|
||||||
|
+ const handle2 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_minus, 3, count });
|
||||||
|
+ defer handle2.join();
|
||||||
|
}
|
||||||
|
// Here we add up the results.
|
||||||
|
std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus});
|
Loading…
Reference in a new issue