Skip to content

Commit efdfa85

Browse files
committed
Updated examples
1 parent 4f9246b commit efdfa85

4 files changed

Lines changed: 121 additions & 32 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
*.a
66
*.so
77
*.o
8+
9+
**/node_modules/

animation/parallelism/src/scenes/code.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { HighlightStyle } from '@codemirror/language';
1111
import { parser as parser_css } from '@lezer/css';
1212
import { parser as parser_cpp } from '@lezer/cpp';
1313

14+
import blockingCode from '../lectures/parallelism.md?snippet=parallelism_blocking/main.cpp';
1415
import asyncCode from '../lectures/parallelism.md?snippet=parallelism_async/main.cpp';
1516
import algorithmsCode from '../lectures/parallelism.md?snippet=parallelism_algorithms/main.cpp';
1617
import tbbCode from '../lectures/parallelism.md?snippet=parallelism_raw_tbb/main.cpp';
@@ -37,7 +38,6 @@ const MyStyle = HighlightStyle.define([
3738
{ tag: tags.arithmeticOperator, color: '#D16969' }, // VSCode Arithmetic Operator color
3839
]);
3940

40-
const CssHighlighter = new LezerHighlighter(parser_css, MyStyle);
4141
const CppHighlighter = new LezerHighlighter(parser_cpp, MyStyle);
4242

4343
function* centerOn(
@@ -117,20 +117,40 @@ export default makeScene2D(function* (view) {
117117

118118
const duration = 1.0;
119119

120+
// 0. Blocking
121+
yield* codeRef().code(blockingCode, 0);
122+
yield* centerOn(codeRef(), DEFAULT, 0, 15);
123+
yield* waitFor(duration);
124+
125+
// Focus on loading an image
126+
yield* centerOn(codeRef(), lines(14, 24), duration, 30);
127+
yield* waitFor(duration);
128+
yield* centerOn(codeRef(), lines(21, 21), duration, 30);
129+
yield* waitFor(duration);
130+
yield* centerOn(codeRef(), DEFAULT, duration, 20);
131+
yield* waitFor(duration);
132+
120133
// 1. std::async Background Task
121-
yield* codeRef().code(asyncCode, 0);
134+
yield* codeRef().code(asyncCode, duration);
135+
yield* centerOn(codeRef(), DEFAULT, duration, 15);
136+
yield* waitFor(duration);
137+
138+
// Focus on loading an image
139+
yield* centerOn(codeRef(), lines(14, 24), duration, 30);
140+
yield* waitFor(duration);
141+
yield* centerOn(codeRef(), lines(21, 21), duration, 30);
122142
yield* waitFor(duration);
123143

124144
// Focus on std::async
125-
yield* centerOn(codeRef(), lines(28, 30), duration, 25);
145+
yield* centerOn(codeRef(), lines(26, 30), duration, 30);
126146
yield* waitFor(duration);
127147

128148
// Focus on future polling
129-
yield* centerOn(codeRef(), lines(34, 42), duration, 25);
149+
yield* centerOn(codeRef(), lines(32, 42), duration, 30);
130150
yield* waitFor(duration);
131151

132152
// Focus on getting
133-
yield* centerOn(codeRef(), lines(45, 47), duration, 25);
153+
yield* centerOn(codeRef(), lines(43, 47), duration, 30);
134154
yield* waitFor(duration);
135155

136156
// 2. Parallel Algorithms
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
22
"extends": "@motion-canvas/2d/tsconfig.project.json",
33
"compilerOptions": {
4-
"baseUrl": "src"
4+
"baseUrl": "src",
5+
"target": "es2020",
6+
"downlevelIteration": true
57
},
6-
"include": ["src"]
7-
}
8+
"include": [
9+
"src"
10+
]
11+
}

lectures/parallelism.md

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,45 @@ With that out of the way, one of the safest way to do concurrency in modern C++
3939

4040
If we have such a heavy task to run, we can use [`std::async`](https://en.cppreference.com/w/cpp/thread/async.html). This function takes a callable (like a [lambda](lambdas.md)) and a [launch policy](https://en.cppreference.com/w/cpp/thread/launch.html), and runs it asynchronously, potentially on a background thread. It returns a `std::future`, which is basically a promise similar to "I don't have the result now, but I will in the *future*."
4141

42-
For the sake of example, let's say our application needs to load a massive image from disk, but we also want to keep the UI responsive and draw a progress bar:
42+
For the sake of example, let's say our application needs to load a massive image from disk (which we will here simulate by sleeping for 5 seconds) but we also want to keep the UI responsive and draw a loading spinner. Let's first see what happens if we just load the image on the main thread:
43+
44+
<!--
45+
`CPP_SETUP_START`
46+
$PLACEHOLDER
47+
`CPP_SETUP_END`
48+
`CPP_COPY_SNIPPET` parallelism_blocking/main.cpp
49+
`CPP_RUN_CMD` CWD:parallelism_blocking c++ -std=c++17 main.cpp
50+
-->
51+
```cpp
52+
#include <chrono>
53+
#include <iostream>
54+
#include <string>
55+
#include <thread>
56+
57+
struct Image {
58+
std::string data = "massive_8k_image_data";
59+
};
60+
61+
Image LoadMassiveImage() {
62+
// Pretend this takes a long time to load a massive image.
63+
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
64+
return Image{};
65+
}
66+
67+
int main() {
68+
std::cout << "Loading massive image..." << std::flush;
69+
// 😱 The main thread is completely frozen here for 5 seconds.
70+
// We can't update any UI, draw a spinner, or do anything else!
71+
const Image img = LoadMassiveImage();
72+
std::cout << " Done!\n";
73+
std::cout << "Image loaded successfully!\n";
74+
return 0;
75+
}
76+
```
77+
78+
When we run this, the application just prints "Loading massive image..." and then hangs for 5 full seconds with no sign of life. The main thread is completely blocked. There is no way for us to draw a spinner or update any other UI element while the image is loading. To the user, the application looks frozen — and that's because it *is* frozen.
79+
80+
Let's fix this! We can use `std::async` to kick off the heavy `LoadMassiveImage` function on a background thread, freeing the main thread to update the UI. While we're at it, let's encapsulate our spinner logic into a reusable `Spinner` class:
4381
4482
<!--
4583
`CPP_SETUP_START`
@@ -50,19 +88,17 @@ $PLACEHOLDER
5088
-->
5189
```cpp
5290
#include <array>
91+
#include <atomic>
5392
#include <chrono>
5493
#include <future>
5594
#include <iostream>
5695
#include <string>
5796
#include <thread>
5897
5998
namespace {
60-
constexpr std::size_t kSpinnerSize = 10;
61-
constexpr std::chrono::milliseconds kLoadTime = std::chrono::milliseconds(5000);
62-
constexpr std::chrono::milliseconds kPollInterval = std::chrono::milliseconds(100);
63-
const std::array<std::string, kSpinnerSize> kSpinner = {
64-
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"};
65-
} // namespace
99+
constexpr std::chrono::milliseconds kLoadTime{5000};
100+
constexpr std::chrono::milliseconds kSpinInterval{100};
101+
} // namespace
66102
67103
struct Image {
68104
std::string data = "massive_8k_image_data";
@@ -74,37 +110,64 @@ Image LoadMassiveImage() {
74110
return Image{};
75111
}
76112
113+
class Spinner {
114+
public:
115+
void Start(const std::string& message) {
116+
message_ = message;
117+
spinning_ = true;
118+
spin_thread_ = std::thread([this] { Spin(); });
119+
}
120+
121+
void Stop(const std::string& done_message) {
122+
spinning_ = false;
123+
if (spin_thread_.joinable()) { spin_thread_.join(); }
124+
std::cout << "\r" << done_message << "\n";
125+
}
126+
127+
private:
128+
void Spin() {
129+
constexpr std::array<const char*, 10> kFrames = {
130+
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"};
131+
int idx = 0;
132+
while (spinning_) {
133+
std::cout << "\r" << kFrames[idx] << " " << message_ << std::flush;
134+
idx = (idx + 1) % kFrames.size();
135+
std::this_thread::sleep_for(kSpinInterval);
136+
}
137+
}
138+
139+
std::string message_;
140+
std::atomic<bool> spinning_{false};
141+
std::thread spin_thread_;
142+
};
143+
77144
int main() {
78145
// 🚀 Kick off the heavy task in the background.
79146
// std::launch::async forces it to run in a new thread.
80147
std::future<Image> future_image =
81148
std::async(std::launch::async, LoadMassiveImage);
82149
83-
int spinner_idx = 0;
150+
// ⏳ Start the spinner on the main thread while we wait.
151+
Spinner spinner;
152+
spinner.Start("Loading massive image...");
84153
85-
// ⏳ While the future is not ready, keep updating our "UI".
86-
while (future_image.wait_for(kPollInterval) !=
87-
std::future_status::ready) {
88-
// This is our "UI" thread, which is free to do other work
89-
// while the image is loading in the background.
90-
std::cout << "\r" << kSpinner[spinner_idx] << " Loading massive image..."
91-
<< std::flush;
92-
spinner_idx = (spinner_idx + 1) % kSpinner.size();
93-
}
94-
95-
// ✅ Clear the spinner and get the result!
96-
std::cout << "\r✅ Loading massive image... Done!\n";
154+
// Wait for the result (blocks main thread, but the spinner
155+
// keeps animating on its own thread).
97156
const Image img = future_image.get();
157+
158+
// ✅ Stop the spinner and show the result!
159+
spinner.Stop("✅ Loading massive image... Done!");
98160
std::cout << "Image loaded successfully!\n";
99161
return 0;
100162
}
101163
```
102164

103165
Here, we do the following:
104-
1. We use `std::async` to start the heavy `LoadMassiveImage` function on a background thread, which returns a `std::future` that holds a promise of a result.
105-
2. We use `wait_for` with a short timeout to check if the task is finished without blocking the main thread.
106-
3. We execute UI logic (the loading spinner in our case) in the main thread while the background thread is busy.
107-
4. We call `.get()` to synchronize and retrieve the final `Image` data once the task is ready.
166+
1. We first see that loading a massive image *without* parallelism completely freezes the main thread — no spinner, no UI updates, nothing.
167+
2. We fix that by using `std::async` to start the heavy `LoadMassiveImage` function on a background thread, which returns a `std::future` that holds a promise of the result.
168+
3. We encapsulate the spinner animation in a `Spinner` class that runs on its own thread, using `std::atomic<bool>` for thread-safe signaling.
169+
4. We call `.get()` on the future to wait for and retrieve the final `Image` data, while the spinner keeps animating independently.
170+
5. Once the image is loaded, we call `spinner.Stop()` to cleanly shut down the animation thread and display the final message.
108171

109172

110173
### Execution Strategies (`std::launch`)

0 commit comments

Comments
 (0)