前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rust那些事之并发Send与Sync

Rust那些事之并发Send与Sync

作者头像
公众号guangcity
发布2023-02-28 15:48:43
7090
发布2023-02-28 15:48:43
举报
文章被收录于专栏:光城(guangcity)

Rust那些事之并发Send与Sync

Send与Sync在Rust中属于marker trait,代码位于marker.rs,在标记模块中还有Copy、Unpin等trait。

在marker.rs中是通过auto trait来实现。

代码语言:javascript
复制
pub unsafe auto trait Sync { }

auto trait又称为opt-in, built-in trait (OIBIT)。这是一种不稳定的特性,每个类型都会自动实现一个特征,除非它们选择退出或包含一个不实现该特征的类型。

换言之,opt-in对应还有个opt-out,可以通过!(negative trait impl)语法来实现。

例如:下面代码中第一行表示类型Wrapper实现了Send,但是却没实现Sync。

代码语言:javascript
复制
unsafe impl Send for Wrapper {}
unsafe impl !Sync for Wrapper {}

本节将会重点讲解Send、Sync相关的并发知识。

1.auto trait

可以通过安装nightly版使用feature特性。

代码语言:javascript
复制
rustup toolchain install nightly  

下面以自定义auto trait实现为例:

代码语言:javascript
复制
#![feature(negative_impls)]
#![feature(auto_traits)]
auto trait IsCool {}

impl !IsCool for String {}

struct MyStruct;
struct HasAString(String);

fn check_cool<C: IsCool>(_: C) {}

调用:

代码语言:javascript
复制
check_cool(42);
check_cool(false);
check_cool(MyStruct);
# the trait `IsCool` is not implemented for `std::string::String`
check_cool(String::new());

这里给了一个简单的例子,展示了auto trait的用法,当没有实现(通过!)auto trait时,编译器会在编译阶段报:the trait XXX is not implemented for YYY。

2.Send与Sync

Send含义:跨线程move,ownership。Sync含义:跨线程share data,borrow。

通常在我们编译多线程代码时,会存在所有权转移、数据共享。那么问题来了,Rc与原生指针是否可以在多线程使用呢?

我们打开Rc(Reference Counting, 引用计数)的源码可以看到这里使用了negative trait,并没有实现Send与Sync,因此通过Rc包裹的对象并不是线程安全的,只能用在单线程中。

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

如果我们将它用在多线程中,会出什么问题呢?

代码语言:javascript
复制
fn main() {
    let val = std::rc::Rc::new(5);
    let t = std::thread::spawn(move || {
        println!("this is a thread val: {}",val);
    });

    t.join().unwrap();
}

报错:

代码语言:javascript
复制
error[E0277]: `Rc<i32>` cannot be sent between threads safely
...
the trait `Send` is not implemented for `Rc<i32>`
...

与之对应,Arc(Atomic Reference Counted, 原子引用计数),可以看一下源码实现:

代码语言:javascript
复制
unsafe impl<T: ?Sized + Sync + Send> Send for Arc<T> {}
unsafe impl<T: ?Sized + Sync + Send> Sync for Arc<T> {}

Send与Sync都实现了。

Send可以实现在多线程间安全传递所有权,Sync可以线程安全的共享数据(例如:引用)。

此外,官方文档:当且仅当类型T的引用&T是Send,T是Sync。

大概意思就是如果引用都无法在多线程之前传递,那么底层数据变无法进行数据共享了。

marker.rs中还有段比较重要的代码,表示原生指针不是线程安全的,没有实现Send、Sync trait。

代码语言:javascript
复制
impl<T: ?Sized> !Send for *const T {}
impl<T: ?Sized> !Send for *mut T {}
impl<T: ?Sized> !Sync for *const T {}
impl<T: ?Sized> !Sync for *mut T {}

Mutex与RwLock

Mutex与RwLock相比于其他语言来说,实现了用户友好的接口,通过new即可将类型传递进去。

代码语言:javascript
复制
Arc::new(Mutex::new(Foo{}))

在Go中使用Mutex,张这个样子:

代码语言:javascript
复制
v   map[string]int
mux sync.Mutex

可以看到rust一行便可以知道保护的是哪个数据。Mutex是用来保护共享变量,所以这个变量类型T我们猜测可以是安全的,也可以是不安全的,所以Sync是不被要求的,因此我们看源码:

代码语言:javascript
复制
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}

Mutex会去实现Send与Sync,要求的类型T一定是具有所有权(实现Send),但是并不要求数据是否是安全的(没实现Sync)。

同理:RwLock是读写锁,需要满足并发读,因此要求T必须实现Sync。

代码语言:javascript
复制
unsafe impl<T: ?Sized + Send> Send for RwLock<T> {}
unsafe impl<T: ?Sized + Send + Sync> Sync for RwLock<T> {}

小知识

前面讲解了raw pointer并不是线程安全的,那么如何实现线程安全呢?

其实也比较简单:可以通过如下多种方法:

  • 自定义类型 将raw pointer包裹起来即可。
代码语言:javascript
复制
struct Wrapper(*mut i32);
unsafe impl Send for Wrapper {}
unsafe impl Sync for Wrapper {}
  • Box 使用智能指针Box。
代码语言:javascript
复制
Box::new(my_num)

本节完~

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

本文分享自 光城 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Rust那些事之并发Send与Sync
  • 1.auto trait
  • 2.Send与Sync
  • Mutex与RwLock
    • 小知识
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档