16 fearless concurrent
Safe and efficient handling of concurrent programming is another major goal of Rust
Memory security and efficient programming have always been the goal of many languages. Rust uses ownership and type system to balance this
In this chapter, we will understand
1. How to create threads to run multi terminal code at the same time
2. Message passing concurrency, where channel is used to pass messages between threads
3. Shared state concurrency, in which multiple threads can access the same piece of data
4.Sync and Send trait extend the concurrency guarantee of Rust to user-defined types and types provided by the standard library
16.2 transferring data between processes using message passing
We use message passing to ensure concurrency security. One of the main tools to realize message passing concurrency in Rust is channel. The channel consists of sender and receiver. When either sender or receiver is discarded, the channel can be considered closed
Let's develop a program that generates values in one thread, sends them to the channel, receives values in another thread and prints them. This technology can help us realize the chat system or use multithreading for distributed computing and send some computing results to a thread for aggregation
use std::sync::mpsc; fn main() { let (tx,rx) = mpsc::channel(); }
Create a channel and assign values at both ends to tx and rx respectively
Note that at this time, we just created a channel, but did not do anything. Of course, we can't compile
Here, use the mpsc::channel function to create a new channel; mpsc is an abbreviation for multiple producers and single consumers. This means that the sender can have multiple senders
mpsc::channel will return a tuple: the first element is the sender and the second element is the receiver. let statements and patterns are used to deconstruct this tuple
Let's try passing a string using a channel
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); }
Running `target/debug/smartPoint` Got:Hi
We first moved tx to a new thread using move, sent a string in the new thread, and then received and printed it in the main thread. Perfect!
Channel and ownership transfer
Ownership rules play an important role in message passing, which helps us write safe concurrent code. Now let's try to continue using val after sending it
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); }
error[E0382]: borrow of moved value: `val` --> src/main.rs:8:30 | 6 | let val = String::from("Hi"); | --- move occurs because `val` has type `String`, which does not implement the `Copy` trait 7 | tx.send(val).unwrap(); | --- value moved here 8 | println!("val is {}",val); | ^^^ value borrowed here after move
Sure enough, because the ownership of values has been transferred and can no longer be used, Rust's ownership rules are so comfortable
Send multiple values and observe the waiting of the receiver
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); } }
Running `target/debug/smartPoint` Got:hi Got:from Got:the Got:thread
We see that sending multiple values can also be received one by one
Create multiple producers by cloning senders
fn main() { 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); } }
We cloned a sender using the cloning method, and the sender can send data in another new thread. This is concurrency! Difficult and interesting