Files
docs-rust/ch16/ch16-02-message-passing.html
2026-06-22 21:27:36 +05:30

329 lines
17 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Transfer Data Between Threads with Message Passing</title>
</head>
<body>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="using-message-passing-to-transfer-data-between-threads"></a></p>
<h2 id="transfer-data-between-threads-with-message-passing"><a class="header" href="#transfer-data-between-threads-with-message-passing">Transfer Data Between Threads with Message Passing</a></h2>
<p>One increasingly popular approach to ensuring safe concurrency is message
passing, where threads or actors communicate by sending each other messages
containing data. Heres the idea in a slogan from <a href="https://golang.org/doc/effective_go.html#concurrency">the Go language documentation</a>:
“Do not communicate by sharing memory; instead, share memory by communicating.”</p>
<p>To accomplish message-sending concurrency, Rusts standard library provides an
implementation of channels. A <em>channel</em> is a general programming concept by
which data is sent from one thread to another.</p>
<p>You can imagine a channel in programming as being like a directional channel of
water, such as a stream or a river. If you put something like a rubber duck
into a river, it will travel downstream to the end of the waterway.</p>
<p>A channel has two halves: a transmitter and a receiver. The transmitter half is
the upstream location where you put the rubber duck into the river, and the
receiver half is where the rubber duck ends up downstream. One part of your
code calls methods on the transmitter with the data you want to send, and
another part checks the receiving end for arriving messages. A channel is said
to be <em>closed</em> if either the transmitter or receiver half is dropped.</p>
<p>Here, well work up to a program that has one thread to generate values and
send them down a channel, and another thread that will receive the values and
print them out. Well be sending simple values between threads using a channel
to illustrate the feature. Once youre familiar with the technique, you could
use channels for any threads that need to communicate with each other, such as
a chat system or a system where many threads perform parts of a calculation and
send the parts to one thread that aggregates the results.</p>
<p>First, in Listing 16-6, well create a channel but not do anything with it.
Note that this wont compile yet because Rust cant tell what type of values we
want to send over the channel.</p>
<figure class="listing" id="listing-16-6">
<span class="file-name">Filename: src/main.rs</span>
<pre><code class="language-rust ignore does_not_compile">use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
}</code></pre>
<figcaption><a href="#listing-16-6">Listing 16-6</a>: Creating a channel and assigning the two halves to <code>tx</code> and <code>rx</code></figcaption>
</figure>
<p>We create a new channel using the <code>mpsc::channel</code> function; <code>mpsc</code> stands for
<em>multiple producer, single consumer</em>. In short, the way Rusts standard library
implements channels means a channel can have multiple <em>sending</em> ends that
produce values but only one <em>receiving</em> end that consumes those values. Imagine
multiple streams flowing together into one big river: Everything sent down any
of the streams will end up in one river at the end. Well start with a single
producer for now, but well add multiple producers when we get this example
working.</p>
<p>The <code>mpsc::channel</code> function returns a tuple, the first element of which is the
sending end—the transmitter—and the second element of which is the receiving
end—the receiver. The abbreviations <code>tx</code> and <code>rx</code> are traditionally used in
many fields for <em>transmitter</em> and <em>receiver</em>, respectively, so we name our
variables as such to indicate each end. Were using a <code>let</code> statement with a
pattern that destructures the tuples; well discuss the use of patterns in
<code>let</code> statements and destructuring in Chapter 19. For now, know that using a
<code>let</code> statement in this way is a convenient approach to extract the pieces of
the tuple returned by <code>mpsc::channel</code>.</p>
<p>Lets move the transmitting end into a spawned thread and have it send one
string so that the spawned thread is communicating with the main thread, as
shown in Listing 16-7. This is like putting a rubber duck in the river upstream
or sending a chat message from one thread to another.</p>
<figure class="listing" id="listing-16-7">
<span class="file-name">Filename: src/main.rs</span>
<pre class="playground"><code class="language-rust edition2024">use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});
}</code></pre>
<figcaption><a href="#listing-16-7">Listing 16-7</a>: Moving <code>tx</code> to a spawned thread and sending <code>"hi"</code></figcaption>
</figure>
<p>Again, were using <code>thread::spawn</code> to create a new thread and then using <code>move</code>
to move <code>tx</code> into the closure so that the spawned thread owns <code>tx</code>. The spawned
thread needs to own the transmitter to be able to send messages through the
channel.</p>
<p>The transmitter has a <code>send</code> method that takes the value we want to send. The
<code>send</code> method returns a <code>Result&lt;T, E&gt;</code> type, so if the receiver has already
been dropped and theres nowhere to send a value, the send operation will
return an error. In this example, were calling <code>unwrap</code> to panic in case of an
error. But in a real application, we would handle it properly: Return to
Chapter 9 to review strategies for proper error handling.</p>
<p>In Listing 16-8, well get the value from the receiver in the main thread. This
is like retrieving the rubber duck from the water at the end of the river or
receiving a chat message.</p>
<figure class="listing" id="listing-16-8">
<span class="file-name">Filename: src/main.rs</span>
<pre class="playground"><code class="language-rust edition2024">use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!("Got: {received}");
}</code></pre>
<figcaption><a href="#listing-16-8">Listing 16-8</a>: Receiving the value <code>"hi"</code> in the main thread and printing it</figcaption>
</figure>
<p>The receiver has two useful methods: <code>recv</code> and <code>try_recv</code>. Were using <code>recv</code>,
short for <em>receive</em>, which will block the main threads execution and wait
until a value is sent down the channel. Once a value is sent, <code>recv</code> will
return it in a <code>Result&lt;T, E&gt;</code>. When the transmitter closes, <code>recv</code> will return
an error to signal that no more values will be coming.</p>
<p>The <code>try_recv</code> method doesnt block, but will instead return a <code>Result&lt;T, E&gt;</code>
immediately: an <code>Ok</code> value holding a message if one is available and an <code>Err</code>
value if there arent any messages this time. Using <code>try_recv</code> is useful if
this thread has other work to do while waiting for messages: We could write a
loop that calls <code>try_recv</code> every so often, handles a message if one is
available, and otherwise does other work for a little while until checking
again.</p>
<p>Weve used <code>recv</code> in this example for simplicity; we dont have any other work
for the main thread to do other than wait for messages, so blocking the main
thread is appropriate.</p>
<p>When we run the code in Listing 16-8, well see the value printed from the main
thread:</p>
<!-- Not extracting output because changes to this output aren't significant;
the changes are likely to be due to the threads running differently rather than
changes in the compiler -->
<pre><code class="language-text">Got: hi
</code></pre>
<p>Perfect!</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="channels-and-ownership-transference"></a></p>
<h3 id="transferring-ownership-through-channels"><a class="header" href="#transferring-ownership-through-channels">Transferring Ownership Through Channels</a></h3>
<p>The ownership rules play a vital role in message sending because they help you
write safe, concurrent code. Preventing errors in concurrent programming is the
advantage of thinking about ownership throughout your Rust programs. Lets do
an experiment to show how channels and ownership work together to prevent
problems: Well try to use a <code>val</code> value in the spawned thread <em>after</em> weve
sent it down the channel. Try compiling the code in Listing 16-9 to see why
this code isnt allowed.</p>
<figure class="listing" id="listing-16-9">
<span class="file-name">Filename: src/main.rs</span>
<pre><code class="language-rust ignore does_not_compile">use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
println!("val is {val}");
});
let received = rx.recv().unwrap();
println!("Got: {received}");
}</code></pre>
<figcaption><a href="#listing-16-9">Listing 16-9</a>: Attempting to use <code>val</code> after weve sent it down the channel</figcaption>
</figure>
<p>Here, we try to print <code>val</code> after weve sent it down the channel via <code>tx.send</code>.
Allowing this would be a bad idea: Once the value has been sent to another
thread, that thread could modify or drop it before we try to use the value
again. Potentially, the other threads modifications could cause errors or
unexpected results due to inconsistent or nonexistent data. However, Rust gives
us an error if we try to compile the code in Listing 16-9:</p>
<pre><code class="language-console">$ cargo run
Compiling message-passing v0.1.0 (file:///projects/message-passing)
error[E0382]: borrow of moved value: `val`
--&gt; src/main.rs:10:27
|
8 | let val = String::from("hi");
| --- move occurs because `val` has type `String`, which does not implement the `Copy` trait
9 | tx.send(val).unwrap();
| --- value moved here
10 | println!("val is {val}");
| ^^^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0382`.
error: could not compile `message-passing` (bin "message-passing") due to 1 previous error
</code></pre>
<p>Our concurrency mistake has caused a compile-time error. The <code>send</code> function
takes ownership of its parameter, and when the value is moved the receiver
takes ownership of it. This stops us from accidentally using the value again
after sending it; the ownership system checks that everything is okay.</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="sending-multiple-values-and-seeing-the-receiver-waiting"></a></p>
<h3 id="sending-multiple-values"><a class="header" href="#sending-multiple-values">Sending Multiple Values</a></h3>
<p>The code in Listing 16-8 compiled and ran, but it didnt clearly show us that
two separate threads were talking to each other over the channel.</p>
<p>In Listing 16-10, weve made some modifications that will prove the code in
Listing 16-8 is running concurrently: The spawned thread will now send multiple
messages and pause for a second between each message.</p>
<figure class="listing" id="listing-16-10">
<span class="file-name">Filename: src/main.rs</span>
<pre><code class="language-rust noplayground">use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {received}");
}
}</code></pre>
<figcaption><a href="#listing-16-10">Listing 16-10</a>: Sending multiple messages and pausing between each one</figcaption>
</figure>
<p>This time, the spawned thread has a vector of strings that we want to send to
the main thread. We iterate over them, sending each individually, and pause
between each by calling the <code>thread::sleep</code> function with a <code>Duration</code> value of
one second.</p>
<p>In the main thread, were not calling the <code>recv</code> function explicitly anymore:
Instead, were treating <code>rx</code> as an iterator. For each value received, were
printing it. When the channel is closed, iteration will end.</p>
<p>When running the code in Listing 16-10, you should see the following output
with a one-second pause in between each line:</p>
<!-- Not extracting output because changes to this output aren't significant;
the changes are likely to be due to the threads running differently rather than
changes in the compiler -->
<pre><code class="language-text">Got: hi
Got: from
Got: the
Got: thread
</code></pre>
<p>Because we dont have any code that pauses or delays in the <code>for</code> loop in the
main thread, we can tell that the main thread is waiting to receive values from
the spawned thread.</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="creating-multiple-producers-by-cloning-the-transmitter"></a></p>
<h3 id="creating-multiple-producers"><a class="header" href="#creating-multiple-producers">Creating Multiple Producers</a></h3>
<p>Earlier we mentioned that <code>mpsc</code> was an acronym for <em>multiple producer, single
consumer</em>. Lets put <code>mpsc</code> to use and expand the code in Listing 16-10 to
create multiple threads that all send values to the same receiver. We can do so
by cloning the transmitter, as shown in Listing 16-11.</p>
<figure class="listing" id="listing-16-11">
<span class="file-name">Filename: src/main.rs</span>
<pre><code class="language-rust noplayground"><span class="boring">use std::sync::mpsc;
</span><span class="boring">use std::thread;
</span><span class="boring">use std::time::Duration;
</span><span class="boring">
</span><span class="boring">fn main() {
</span> // --snip--
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
thread::spawn(move || {
let vals = vec![
String::from("more"),
String::from("messages"),
String::from("for"),
String::from("you"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {received}");
}
// --snip--
<span class="boring">}</span></code></pre>
<figcaption><a href="#listing-16-11">Listing 16-11</a>: Sending multiple messages from multiple producers</figcaption>
</figure>
<p>This time, before we create the first spawned thread, we call <code>clone</code> on the
transmitter. This will give us a new transmitter we can pass to the first
spawned thread. We pass the original transmitter to a second spawned thread.
This gives us two threads, each sending different messages to the one receiver.</p>
<p>When you run the code, your output should look something like this:</p>
<!-- Not extracting output because changes to this output aren't significant;
the changes are likely to be due to the threads running differently rather than
changes in the compiler -->
<pre><code class="language-text">Got: hi
Got: more
Got: from
Got: messages
Got: for
Got: the
Got: thread
Got: you
</code></pre>
<p>You might see the values in another order, depending on your system. This is
what makes concurrency interesting as well as difficult. If you experiment with
<code>thread::sleep</code>, giving it various values in the different threads, each run
will be more nondeterministic and create different output each time.</p>
<p>Now that weve looked at how channels work, lets look at a different method of
concurrency.</p>
</body>
</html>