From 3111eda07a4a4692bf69e3aaad999d840ac9c138 Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Mon, 7 Oct 2024 13:25:41 -0600 Subject: [PATCH] Ch. 17: create a diagram showing blocked parallelism --- dot/trpl17-03.dot | 35 +++++++--- dot/trpl17-04.dot | 31 +++------ dot/trpl17-05.dot | 52 ++++++-------- dot/trpl17-06.dot | 58 +++++---------- dot/trpl17-07.dot | 73 ++++++++++++------- dot/trpl17-08.dot | 41 +++-------- dot/trpl17-09.dot | 62 +++++++++++++++++ nostarch/chapter17.md | 46 ++++++------ src/ch17-00-async-await.md | 18 +++-- src/ch17-05-traits-for-async.md | 32 ++++----- src/img/trpl17-03.svg | 120 ++++++++++++++++++++++++++------ 11 files changed, 348 insertions(+), 220 deletions(-) create mode 100644 dot/trpl17-09.dot diff --git a/dot/trpl17-03.dot b/dot/trpl17-03.dot index 6c63b7bde4..7d5a4971cf 100644 --- a/dot/trpl17-03.dot +++ b/dot/trpl17-03.dot @@ -1,19 +1,32 @@ digraph { - rankdir = RL; - overlap = false; dpi = 300.0; + + rankdir = "LR"; splines = false; cluster = true; - node [shape = "plaintext";]; + node [shape = diamond;]; + + // The graphs end up with the correct order, i.e. Task 1 *above* Task 2, when + // this is first. + subgraph cluster_ColleagueB { + label = "Task A"; + A1; + A2; + A0_1 [style = invis;]; + A3; + + A1 -> A2; + A2 -> A0_1 [arrowhead = "tee"; headport = "A0_1:c"; headclip = false;]; + A0_1; + A0_1 -> A3 [dir = both; arrowtail = "tee"; tailclip = false;]; + } - fut1 [label = < - - - - -
fut1
0
1
>;]; + subgraph cluster_ColleagueA { + newrank = true; + label = "Task B"; + B1 -> B2 -> B3 -> B4; + } - edge [tailclip = "false";]; - fut1:source:c -> fut1:target [dir = forward;]; + B3 -> A3; } \ No newline at end of file diff --git a/dot/trpl17-04.dot b/dot/trpl17-04.dot index afcf8e77a1..6c63b7bde4 100644 --- a/dot/trpl17-04.dot +++ b/dot/trpl17-04.dot @@ -7,26 +7,13 @@ digraph { node [shape = "plaintext";]; - // Group the two together, which results in the desired alignment. - subgraph { - // But don't show the frame! - style = "invis"; - - fut1 [label = < - - - - -
fut1
?
?
?
>;]; - - fut2 [label = < - - - - -
fut2
0
1
>;]; - - edge [tailclip = "false"; dir = forward]; - fut2:source:c -> fut1:target:e; - } + fut1 [label = < + + + + +
fut1
0
1
>;]; + + edge [tailclip = "false";]; + fut1:source:c -> fut1:target [dir = forward;]; } \ No newline at end of file diff --git a/dot/trpl17-05.dot b/dot/trpl17-05.dot index e036a385c3..afcf8e77a1 100644 --- a/dot/trpl17-05.dot +++ b/dot/trpl17-05.dot @@ -1,42 +1,32 @@ digraph { - rankdir = LR; + rankdir = RL; + overlap = false; dpi = 300.0; + splines = false; + cluster = true; node [shape = "plaintext";]; - pinned_box [label = < - - -
Pin
>;]; - - subgraph cluster_box { - label = ""; - peripheries = 0; + // Group the two together, which results in the desired alignment. + subgraph { + // But don't show the frame! + style = "invis"; - subgraph cluster_box_internal { - peripheries = 1; - label = "b1"; - shape = box; - style = solid; - pin [shape = "point";]; - } - } - - subgraph cluster_deref { - style = bold; - label = "pinned"; + fut1 [label = < + + + + +
fut1
?
?
?
>;]; - box [label = < - - + fut2 [label = <
fut
0
+ + + - -
fut2
0
1
...
1
>;]; + + edge [tailclip = "false"; dir = forward]; + fut2:source:c -> fut1:target:e; } - - edge [tailclip = false;]; - pinned_box -> pin [tailport = "source:c"; arrowhead = "none";]; - pin -> box [headport = "target";]; - box -> box [tailport = "source:c"; headport = "internal";]; } \ No newline at end of file diff --git a/dot/trpl17-06.dot b/dot/trpl17-06.dot index 11b79574c4..e036a385c3 100644 --- a/dot/trpl17-06.dot +++ b/dot/trpl17-06.dot @@ -1,50 +1,32 @@ digraph { rankdir = LR; - newrank = true; dpi = 300.0; node [shape = "plaintext";]; + pinned_box [label = < + + +
Pin
>;]; - subgraph cluster_not_fut { + subgraph cluster_box { + label = ""; peripheries = 0; - pin [label = < - - -
Pin
>;]; - - subgraph cluster_boxes { - peripheries = 0; - rank = same; - - subgraph cluster_box_1 { - subgraph cluster_box_2_internal { - label = "b1"; - shape = box; - style = solid; - style = filled; - peripheries = 1; - box1 [shape = "point";style = "invis";]; - } - } - - subgraph cluster_box_2 { - subgraph cluster_box_2_internal { - label = "b2"; - shape = box; - style = solid; - peripheries = 1; - box2 [shape = "point";]; - } - } + subgraph cluster_box_internal { + peripheries = 1; + label = "b1"; + shape = box; + style = solid; + pin [shape = "point";]; } } - subgraph cluster_target { + + subgraph cluster_deref { style = bold; label = "pinned"; - fut [label = < + box [label = <
@@ -53,12 +35,8 @@ digraph {
fut
0
>;]; } - - box1 -> box2 [rankdir = TB; style = invis;]; - edge [tailclip = false;]; - pin -> box1 [style = "invis";]; - pin -> box2 [tailport = "source:c"; arrowhead = "none";]; - box2 -> fut [headport = "target";]; - fut -> fut [tailport = "source:c"; headport = "internal";]; + pinned_box -> pin [tailport = "source:c"; arrowhead = "none";]; + pin -> box [headport = "target";]; + box -> box [tailport = "source:c"; headport = "internal";]; } \ No newline at end of file diff --git a/dot/trpl17-07.dot b/dot/trpl17-07.dot index 8e9897f46d..11b79574c4 100644 --- a/dot/trpl17-07.dot +++ b/dot/trpl17-07.dot @@ -1,39 +1,64 @@ digraph { rankdir = LR; - overlap = false; - dpi = 300.0; - splines = false; - cluster = true; newrank = true; - outputorder = in; - compound = true; - labelloc = "c"; + dpi = 300.0; node [shape = "plaintext";]; - pinned_box [label = < - - -
Pin
>;]; - - subgraph cluster_deref { - style = dashed; - label = "String"; + subgraph cluster_not_fut { + peripheries = 0; + + pin [label = < + + +
Pin
>;]; - pin [shape = "point";]; + subgraph cluster_boxes { + peripheries = 0; + rank = same; + + subgraph cluster_box_1 { + subgraph cluster_box_2_internal { + label = "b1"; + shape = box; + style = solid; + style = filled; + peripheries = 1; + box1 [shape = "point";style = "invis";]; + } + } + + subgraph cluster_box_2 { + subgraph cluster_box_2_internal { + label = "b2"; + shape = box; + style = solid; + peripheries = 1; + box2 [shape = "point";]; + } + } + } + } + subgraph cluster_target { + style = bold; + label = "pinned"; fut [label = < - - - - - - + + + + +
5usizehello
fut
0
...
1
>;]; } + + box1 -> box2 [rankdir = TB; style = invis;]; + edge [tailclip = false;]; - pinned_box -> pin [tailport = "source:c"; arrowhead = "none";]; - pin -> fut [headport = "target";]; + pin -> box1 [style = "invis";]; + pin -> box2 [tailport = "source:c"; arrowhead = "none";]; + box2 -> fut [headport = "target";]; + fut -> fut [tailport = "source:c"; headport = "internal";]; } \ No newline at end of file diff --git a/dot/trpl17-08.dot b/dot/trpl17-08.dot index de73007511..8e9897f46d 100644 --- a/dot/trpl17-08.dot +++ b/dot/trpl17-08.dot @@ -16,47 +16,24 @@ digraph { >;]; - subgraph cluster_both { - peripheries = 0; - + + subgraph cluster_deref { + style = dashed; + label = "String"; + pin [shape = "point";]; - string1 [label = < - - - + fut [label = <
s1
5usize
+ - - +
5usize h e l lo
o
>;]; - - subgraph cluster_deref { - style = dashed; - label = "String"; - peripheries = 1; - - pin [shape = "point";]; - - string2 [label = < - - - - - - - - - - - -
s2
7usizegoodbye
>;]; - } } edge [tailclip = false;]; pinned_box -> pin [tailport = "source:c"; arrowhead = "none";]; - pin -> string2 [headport = "target";]; + pin -> fut [headport = "target";]; } \ No newline at end of file diff --git a/dot/trpl17-09.dot b/dot/trpl17-09.dot new file mode 100644 index 0000000000..de73007511 --- /dev/null +++ b/dot/trpl17-09.dot @@ -0,0 +1,62 @@ +digraph { + rankdir = LR; + overlap = false; + dpi = 300.0; + splines = false; + cluster = true; + newrank = true; + outputorder = in; + compound = true; + labelloc = "c"; + + node [shape = "plaintext";]; + + pinned_box [label = < + + +
Pin
>;]; + + subgraph cluster_both { + peripheries = 0; + + + + string1 [label = < + + + + + + + + + +
s1
5usizehello
>;]; + + subgraph cluster_deref { + style = dashed; + label = "String"; + peripheries = 1; + + pin [shape = "point";]; + + string2 [label = < + + + + + + + + + + + +
s2
7usizegoodbye
>;]; + } + } + + edge [tailclip = false;]; + pinned_box -> pin [tailport = "source:c"; arrowhead = "none";]; + pin -> string2 [headport = "target";]; +} \ No newline at end of file diff --git a/nostarch/chapter17.md b/nostarch/chapter17.md index 45757777d4..a8149cf5c6 100644 --- a/nostarch/chapter17.md +++ b/nostarch/chapter17.md @@ -89,7 +89,7 @@ tasks by switching between them. Concurrent work flow -Figure 17-1: A concurrent workflow, switching between Task A and Task B +Figure 17-1: A concurrent workflow, switching between Task A and Task B. When you agree to split up a group of tasks between the people on the team, with each person taking one task and working on it alone, this is *parallelism*. Each @@ -98,15 +98,21 @@ person on the team can make progress at the exact same time. Concurrent work flow Figure 17-2: A parallel workflow, where work happens on Task A and Task B -independently +independently. With both of these situations, you might have to coordinate between different tasks. Maybe you *thought* the task that one person was working on was totally independent from everyone else’s work, but it actually needs something finished by another person on the team. Some of the work could be done in parallel, but some of it was actually *serial*: it could only happen in a series, one thing -after the other. Likewise, you might realize that one of your own tasks depends -on another of your tasks. Now your concurrent work has also become serial. +after the other, as in Figure 17-3. + +Concurrent work flow + +Figure 17-3: A partially parallel workflow, where work happens on Task A and Task B independently until task A3 is blocked on the results of task B3. + +Likewise, you might realize that one of your own tasks depends on another of +your tasks. Now your concurrent work has also become serial. Parallelism and concurrency can intersect with each other, too. If you learn that a colleague is stuck until you finish one of your tasks, you’ll probably @@ -2601,13 +2607,13 @@ When we move a future—whether by pushing into a data structure to use as an iterator with `join_all`, or returning them from a function—that actually means moving the state machine Rust creates for us. And unlike most other types in Rust, the futures Rust creates for async blocks can end up with references to -themselves in the fields of any given variant, as in Figure 17-3 (a simplified +themselves in the fields of any given variant, as in Figure 17-4 (a simplified illustration to help you get a feel for the idea, rather than digging into what are often fairly complicated details). -Concurrent work flow +Concurrent work flow -Figure 17-3: A self-referential data type. +Figure 17-4: A self-referential data type. By default, though, any object which has a reference to itself is unsafe to move, because references always point to the actual memory address of the thing @@ -2618,9 +2624,9 @@ the data structure. For another—and more importantly!—the computer is now fr to reuse that memory for other things! You could end up reading completely unrelated data later. -Concurrent work flow +Concurrent work flow -Figure 17-4: The unsafe result of moving a self-referential data type. +Figure 17-5: The unsafe result of moving a self-referential data type. In principle, the Rust compiler could try to update every reference to an object every time it gets moved. That would potentially be a lot of performance @@ -2633,25 +2639,25 @@ which has any active references to it using safe code. `Pin` builds on that to give us the exact guarantee we need. When we *pin* a value by wrapping a pointer to that value in `Pin`, it can no longer move. Thus, if you have `Pin>`, you actually pin the `SomeType` value, *not* -the `Box` pointer. Figure 17-5 illustrates this: +the `Box` pointer. Figure 17-6 illustrates this: -Concurrent work flow +Concurrent work flow -Figure 17-5: Pinning a `Box` which points to a self-referential future type. +Figure 17-6: Pinning a `Box` which points to a self-referential future type. In fact, the `Box` pointer can still move around freely. Remember: we care about making sure the data ultimately being referenced stays in its place. If a pointer moves around, but the data it points to is in the same place, as in -Figure 17-6, there’s no potential problem. (How you would do this with a `Pin` +Figure 17-7, there’s no potential problem. (How you would do this with a `Pin` wrapping a `Box` is more than we’ll get into in this particular discussion, but it would make for a good exercise! If you look at the docs for the types as well as the `std::pin` module, you might be able to work out how you would do that.) The key is that the self-referential type itself cannot move, because it is still pinned. -Concurrent work flow +Concurrent work flow -Figure 17-6: Moving a `Box` which points to a self-referential future type. +Figure 17-7: Moving a `Box` which points to a self-referential future type. However, most types are perfectly safe to move around, even if they happen to be behind a `Pin` pointer. We only need to think about pinning when items have @@ -2683,19 +2689,19 @@ To make that concrete, think about a `String`: it has a length and the Unicode characters which make it up. We can wrap a `String` in `Pin`, as seen in Figure 17-7. However -Concurrent work flow +Concurrent work flow -Figure 17-7: Pinning a String, with a dotted line indicating that the String +Figure 17-8: Pinning a String, with a dotted line indicating that the String implements the `Unpin` trait, so it is not pinned. This means that we can do things such as replace one string with another at the -exact same location in memory as in Figure 17-8. This doesn’t violate the `Pin` +exact same location in memory as in Figure 17-9. This doesn’t violate the `Pin` contract because `String`—like most other types in Rust—implements `Unpin`, because it has no internal references that make it unsafe to move around! -Concurrent work flow +Concurrent work flow -Figure 17-8: Replacing the String with an entirely different String in memory. +Figure 17-9: Replacing the String with an entirely different String in memory. Now we know enough to understand the errors reported for that `join_all` call from back in Listing 17-17. We originally tried to move the futures produced by diff --git a/src/ch17-00-async-await.md b/src/ch17-00-async-await.md index 920486d333..5fe7020224 100644 --- a/src/ch17-00-async-await.md +++ b/src/ch17-00-async-await.md @@ -89,7 +89,7 @@ tasks by switching between them. Concurrent work flow -
Figure 17-1: A concurrent workflow, switching between Task A and Task B
+
Figure 17-1: A concurrent workflow, switching between Task A and Task B.
@@ -101,7 +101,7 @@ person on the team can make progress at the exact same time. Concurrent work flow -
Figure 17-2: A parallel workflow, where work happens on Task A and Task B independently
+
Figure 17-2: A parallel workflow, where work happens on Task A and Task B independently.
@@ -110,8 +110,18 @@ tasks. Maybe you *thought* the task that one person was working on was totally independent from everyone else’s work, but it actually needs something finished by another person on the team. Some of the work could be done in parallel, but some of it was actually *serial*: it could only happen in a series, one thing -after the other. Likewise, you might realize that one of your own tasks depends -on another of your tasks. Now your concurrent work has also become serial. +after the other, as in Figure 17-3. + +
+ +Concurrent work flow + +
Figure 17-3: A partially parallel workflow, where work happens on Task A and Task B independently until task A3 is blocked on the results of task B3.
+ +
+ +Likewise, you might realize that one of your own tasks depends on another of +your tasks. Now your concurrent work has also become serial. Parallelism and concurrency can intersect with each other, too. If you learn that a colleague is stuck until you finish one of your tasks, you’ll probably diff --git a/src/ch17-05-traits-for-async.md b/src/ch17-05-traits-for-async.md index a7c5feee50..746264d861 100644 --- a/src/ch17-05-traits-for-async.md +++ b/src/ch17-05-traits-for-async.md @@ -224,15 +224,15 @@ When we move a future—whether by pushing into a data structure to use as an iterator with `join_all`, or returning them from a function—that actually means moving the state machine Rust creates for us. And unlike most other types in Rust, the futures Rust creates for async blocks can end up with references to -themselves in the fields of any given variant, as in Figure 17-3 (a simplified +themselves in the fields of any given variant, as in Figure 17-4 (a simplified illustration to help you get a feel for the idea, rather than digging into what are often fairly complicated details).
-Concurrent work flow +Concurrent work flow -
Figure 17-3: A self-referential data type.
+
Figure 17-4: A self-referential data type.
@@ -247,9 +247,9 @@ unrelated data later.
-Concurrent work flow +Concurrent work flow -
Figure 17-4: The unsafe result of moving a self-referential data type.
+
Figure 17-5: The unsafe result of moving a self-referential data type.
@@ -264,20 +264,20 @@ which has any active references to it using safe code. `Pin` builds on that to give us the exact guarantee we need. When we *pin* a value by wrapping a pointer to that value in `Pin`, it can no longer move. Thus, if you have `Pin>`, you actually pin the `SomeType` value, *not* -the `Box` pointer. Figure 17-5 illustrates this: +the `Box` pointer. Figure 17-6 illustrates this:
-Concurrent work flow +Concurrent work flow -
Figure 17-5: Pinning a `Box` which points to a self-referential future type.
+
Figure 17-6: Pinning a `Box` which points to a self-referential future type.
In fact, the `Box` pointer can still move around freely. Remember: we care about making sure the data ultimately being referenced stays in its place. If a pointer moves around, but the data it points to is in the same place, as in -Figure 17-6, there’s no potential problem. (How you would do this with a `Pin` +Figure 17-7, there’s no potential problem. (How you would do this with a `Pin` wrapping a `Box` is more than we’ll get into in this particular discussion, but it would make for a good exercise! If you look at the docs for the types as well as the `std::pin` module, you might be able to work out how you would do @@ -286,9 +286,9 @@ is still pinned.
-Concurrent work flow +Concurrent work flow -
Figure 17-6: Moving a `Box` which points to a self-referential future type.
+
Figure 17-7: Moving a `Box` which points to a self-referential future type.
@@ -323,22 +323,22 @@ characters which make it up. We can wrap a `String` in `Pin`, as seen in Figure
-Concurrent work flow +Concurrent work flow -
Figure 17-7: Pinning a String, with a dotted line indicating that the String implements the `Unpin` trait, so it is not pinned.
+
Figure 17-8: Pinning a String, with a dotted line indicating that the String implements the `Unpin` trait, so it is not pinned.
This means that we can do things such as replace one string with another at the -exact same location in memory as in Figure 17-8. This doesn’t violate the `Pin` +exact same location in memory as in Figure 17-9. This doesn’t violate the `Pin` contract because `String`—like most other types in Rust—implements `Unpin`, because it has no internal references that make it unsafe to move around!
-Concurrent work flow +Concurrent work flow -
Figure 17-8: Replacing the String with an entirely different String in memory.
+
Figure 17-9: Replacing the String with an entirely different String in memory.
diff --git a/src/img/trpl17-03.svg b/src/img/trpl17-03.svg index fed6c36040..ad105a5830 100644 --- a/src/img/trpl17-03.svg +++ b/src/img/trpl17-03.svg @@ -1,30 +1,110 @@ - - - - - + + + + +cluster_ColleagueB + +Task A + + +cluster_ColleagueA + +Task B + + -fut1 - -fut1 - -0 - -1 - -   - - +A1 + +A1 + + + +A2 + +A2 + + -fut1:c->fut1:target - - +A1->A2 + + + + + + +A2->A0_1:c + + + + + + +A3 + +A3 + + + +A0_1->A3 + + + + + + + +B1 + +B1 + + + +B2 + +B2 + + + +B1->B2 + + + + + +B3 + +B3 + + + +B2->B3 + + + + + +B3->A3 + + + + + +B4 + +B4 + + + +B3->B4 + +