introduction
This article introduces several concepts related to Rust concurrency security: the relationship between Send, Sync, Arc, Mutex, RwLock, etc. This is the first of them, which mainly introduces Send and Sync.
Rust's concept of ownership
Before introducing several concepts related to concurrency, it is necessary to understand the concept of ownership of Rust, which has clear restrictions on the ownership of value:
- A value can only have one owner.
- Multiple shared immutable reference s of the same value can exist at the same time.
- However, there can only be mutable reference s of one value.
For example, in the following code, after creating a thread, the user is move d to two different threads:
fn main() { let user = User { name: "drogus".to_string() }; let t1 = spawn(move || { println!("Hello from the first thread {}", user.name); }); let t2 = spawn(move || { println!("Hello from the second thread {}", user.name); }); t1.join().unwrap(); t2.join().unwrap(); }
Because a value can only have one owner, the compiler reports an error, and the error information is as follows:
error[E0382]: use of moved value: `user.name` --> src/main.rs:15:20 | 11 | let t1 = spawn(move || { | ------- value moved into closure here 12 | println!("Hello from the first thread {}", user.name); | --------- variable moved due to use in closure ... 15 | let t2 = spawn(move || { | ^^^^^^^ value used here after move 16 | println!("Hello from the second thread {}", user.name); | --------- use occurs due to use in closure | = note: move occurs because `user.name` has type `String`, which does not implement the `Copy` trait
Binding effect of Send and Sync
Therefore, if a type will be used by multiple threads, its shared properties need to be clearly specified. This is the function of Send and Sync. Note that these two traits are std::marker. There is no need to implement corresponding methods to implement these two traits. It can be understood that these two traits are type constraints, and the compiler checks the type at compile time through these constraints. So far, without understanding the two concepts for the time being, let's take a look at how they play a restrictive role in type checking. For example, std::thread::spawn() is defined as follows:
pub fn spawn<F, T>(f: F) -> JoinHandle<T> where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static,
You can see that the Send constraint is required for the function passed in and the type returned by spawn. Combined with the previous definition of Send:
- Function type F needs to meet the Send constraint: This is because after the thread is created, the function type needs to be passed into the newly created thread, so ownership is required to be passed between threads.
- The return type needs to meet the Send constraint: This is because after the thread is created, the return value also needs to be transferred back to the original thread.
With type constraints, the compiler will check the type when calling the std::thread::spawn function, such as the following code:
#[derive(Debug)] struct Foo {} impl !Send for Foo {} fn main() { let foo = Foo {}; std::thread::spawn(move || { dbg!(foo); }); }
The type Foo tag does not implement the trait of Send, so an error is reported during compilation:
error[E0277]: `Foo` cannot be sent between threads safely --> src/main.rs:7:5 | 7 | std::thread::spawn(move || { | _____^^^^^^^^^^^^^^^^^^_- | | | | | `Foo` cannot be sent between threads safely 8 | | dbg!(foo); 9 | | }); | |_____- within this `[closure@src/main.rs:7:24: 9:6]` | = help: within `[closure@src/main.rs:7:24: 9:6]`, the trait `Send` is not implemented for `Foo` = note: required because it appears within the type `[closure@src/main.rs:7:24: 9:6]` note: required by a bound in `spawn`
If you put impl! Remove the line send for foo {} and the code can be compiled.
There is another knowledge point above: all types meet the send and Sync constraints by default, until the display declaration does not meet this constraint, such as impl above! Send is such a display declaration. This leads to a question: can you play some tricks with the compiler, clearly a certain type does not meet this constraint, and turn a blind eye to see if you can muddle through with the compiler?
The answer is No. the compiler will check all the members contained in this type. Only if all the members meet this constraint, the type can be considered to meet the constraint. You can continue to experiment on the basis of the above and add a Rc type member to the Foo structure:
#[derive(Debug)] struct Foo { rc: Option<std::rc::Rc<i32>>, } fn main() { let foo = Foo { rc: None }; std::thread::spawn(move || { dbg!(foo); }); }
Since Rc does not satisfy the send constraint (that is, impl! Send is declared, see: impl send 1 )As a result, the type Foo cannot muddle through and meet the Send constraint. When compiling the above code, the error message is as follows:
error[E0277]: `Rc<i32>` cannot be sent between threads safely --> src/main.rs:8:5 | 8 | std::thread::spawn(move || { | _____^^^^^^^^^^^^^^^^^^_- | | | | | `Rc<i32>` cannot be sent between threads safely 9 | | dbg!(foo); 10 | | }); | |_____- within this `[closure@src/main.rs:8:24: 10:6]` | = help: within `[closure@src/main.rs:8:24: 10:6]`, the trait `Send` is not implemented for `Rc<i32>` = note: required because it appears within the type `Option<Rc<i32>>` note: required because it appears within the type `Foo`
Therefore, a type must satisfy a constraint if and only if all members of the type satisfy the constraint. Understand Send and Sync trait
Understand Send and Sync trait
Go back to Send and Sync, which are defined in the official document of t rust as follows:
- Send: Types that can be transferred across thread boundaries.
- Sync: Types for which it is safe to share references between threads.
The above definition is translated:
- The Send flag indicates that ownership of this type can be passed between threads.
- The Sync flag indicates that this type of reference can be safely shared among multiple threads.
I find that the above explanation is still a little difficult to understand. We can use a more straightforward way to explain these two kinds of constraints:
Send:
- The type that meets the Send constraint can be safely used exclusively among multiple threads (exclusive access is thread safe).
- The type t that satisfies the Send constraint means that T and &mut t (MUT means that the reference can be modified, or even the data can be deleted, that is, drop) can be passed between multiple threads. To put it bluntly, it can move the value between multiple threads and modify the referenced value.
Sync:
- The type that meets the Sync constraint can be safely shared between multiple threads (shared access is thread safe).
- The type T that satisfies the Sync constraint only means that the type can be read and shared in multiple threads, that is, it cannot be move d or modified, and this value can only be read by referencing &T.
With the above definition, we can know that a reference of type T can meet the Sync constraint only if it meets the Send constraint (a type T is Sync if and only if &t is Send). Namely: T: Sync &t: Send.
For those primitive types, such as i32 types, most of them meet the constraints of Send and Sync at the same time, because the shared references (&) of these types can be used in multiple threads and can also be modified in multiple threads (&mut).
After understanding the two kinds of constraints Send and Sync, you can then look at the application in concurrency security. This is the content of the next article.
reference material
- Arc and Mutex in Rust | It's all about the bit2
- Sync in std::marker - Rust3
- Send in std::marker - Rust4
- Send and Sync - The Rustonomicon5
- rust - Understanding the Send trait - Stack Overflow6
- Understanding Rust Thread Safety7
- An unsafe tour of Rust's Send and Sync | nyanpasu64's blog8
- Rust: A unique perspective9
About Databend
Databend is an open-source, flexible, low-cost data warehouse that can do real-time analysis based on object storage. We look forward to your attention and explore cloud native data warehouse solutions to create a new generation of open source Data Cloud.
- Databend document: https://databend.rs/
- Twitter: https://twitter.com/Datafuse_...
- Slack: https://datafusecloud.slack.com/
- Wechat: Databend
- GitHub : https://github.com/datafusela...
The article was first published on official account: Databend
- https://doc.rust-lang.org/std... ↩
- https://itsallaboutthebit.com... ↩
- https://doc.rust-lang.org/std... ↩
- https://doc.rust-lang.org/std... ↩
- https://doc.rust-lang.org/nom... ↩
- https://stackoverflow.com/que... ↩
- https://onesignal.com/blog/th... ↩
- https://nyanpasu64.github.io/... ↩
- https://limpet.net/mbrubeck/2... ↩