前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊Rust的并发约束:Send和Sync

聊聊Rust的并发约束:Send和Sync

作者头像
newbmiao
发布2023-11-27 12:31:50
2160
发布2023-11-27 12:31:50
举报
文章被收录于专栏:学点Rust学点Rust

不知道你有没有好奇过,Rust是怎么控制并发安全的。为什么编译器在编译时就能发现一些并发安全的问题。

今天拿例子聊聊这背后Rust的两个并发约束traitSyncSend,看看它们是怎么控制并发安全的。

文章目录

  • Send
  • Sync

Send

先来看看下边代码,尝试将String类型的引用计数aRc<String>)移动到另一个线程中去,会发现编译器报错了。

代码语言:javascript
复制
use std::{rc::Rc, thread};
fn main() {
    let a = Rc::new(String::from("hello"));
    // 注意!这里move让闭包获取了a的所有权(Rc<String>)
    thread::spawn(move || {
        let b = a.clone();
        println!("b = {}", b);
    });
    // got error:
    // `Rc<String>` cannot be sent between threads safely
    // the trait `Send` is not implemented for `Rc<String>`
}

仔细观察编译器的报错和下边相关代码trait实现

代码语言:javascript
复制
impl<T: ?Sized> !Send for Rc<T> {}

pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
    F: FnOnce() -> T,
    F: Send + 'static,
    T: Send + 'static,

你会发现thread::spawn要求传入的闭包F必须实现Send,而Rc类型的a没有实现Send,所以编译器报错了。

我们知道Rc是引用计数,它为了性能没有实现原子操作的引用计数,如果在多个线程中共享,那么引用计数可能会出现计数错误,所以不能安全的跨线程共享。

Send是干什么的呢?

Send是一个trait,它标记了实现它的类型可以安全的在线程间传递所有权。也就是可以安全的移动move)其所有权。

Send trait是一个标记型(marker)trait, 它没有实际方法,也不需要用户主动去实现,一般基本类型都实现了Send。而复合类型如果包含的所有成员都实现了Send,那么它也自动实现了Send。(后面的Sync也是这样的自动trait

也就是说,需要并发中需要安全传递都需要被标记实现Send,否则编译器会报错。

并发安全检查变成了trait bound检查,这样就能在编译时发现问题,而不是在运行时,是不是很巧妙!

Sync

再来看看下边代码,尝试将String类型的引用计数a&Rc<String>)共享到一个线程中去,会发现编译器报错了。(注意没有移动,是共享)

代码语言:javascript
复制
use std::{rc::Rc, thread};

fn main() {
    let a = Rc::new(String::from("hello"));
    // 注意!这里没有用move,闭包获取的是a的引用(&Rc<String>)
    thread::spawn(|| {
        let b = a.clone();
        println!("b = {}", b);
    });
    // `Rc<String>` cannot be shared between threads safely
    // the trait `Sync` is not implemented for `Rc<String>`
    // required for `&Rc<String>` to implement `Send`
}

自然,Rc没有实现Sync,所有编译器报错了。

代码语言:javascript
复制
impl<T: ?Sized> !Sync for Rc<T> {}

Sync也是一个标记型trait,它标记了实现它的类型可以安全的在线程间共享访问。

所谓共享,其实就是可以安全的引用。而如果&T实现了Send(可安全移动),那么T就实现了Sync(可安全共享其的引用)。

也就是说,需要并发中需要安全引用&T)都需要T被标记实现了Sync,否则编译器会报错。

又是一个巧妙的设计,通过trait bound检查了引用是否满足并发安全。

总结一下:

  • Send标记了实现它的类型可以安全的在线程间传递所有权(move)。
  • Sync标记了实现它的类型可以安全的在线程间共享引用(&T)。

最后推荐看看官方的这两篇文档来加深理解

  • Extensible Concurrency with the Sync and Send Traits[1]
  • Send and Sync[2]

参考资料

[1]

Extensible Concurrency with the Sync and Send Traits: https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html

[2]

Send and Sync: https://doc.rust-lang.org/nomicon/send-and-sync.html


推荐阅读

如果有用,点个 在看,让更多人看到

外链不能跳转,戳 阅读原文 查看参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 菜鸟Miao 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Send
  • Sync
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档