Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust中的sync和send #71

Open
BruceChen7 opened this issue Jun 8, 2023 · 0 comments
Open

rust中的sync和send #71

BruceChen7 opened this issue Jun 8, 2023 · 0 comments
Labels

Comments

@BruceChen7
Copy link
Owner

参考资料

如何保证共享数据多线程安全

解决的办法也很简单(理论上):

  • 共享只读数据。
  • 不共享数据。
  • 保护好共享的数据
  • 保证数据被访问的时候是valid的

共享只读数据

  • 共享只读数据就是在多线程中,只能对变量进行读操作,不能进行写操作。
  • 只对数据进行只读操作呢? 是Rust类型系统里面规定了,引用在任何时候只能是下面两种情况之一,而不能同时存在:
    • 有任意个只读引用
    • 有一个可变引用(通过这个引用可以修改引用的变量)
  • 这样就会防止数据在一个线程被写,而在另外的线程被读取或者写。

(concept:: Sync 和 Send, 'static))

  • 除了任何时候只能有任意个只读引用或者仅有一个可变引用(不能同时共存)的限制外
  • Rust对多线程的保证,还在于 Sync和Send, 'static

Send

  • 不共享数据就是**不要在多线程中共享变量,

  • Rust对此在编译器做了保证,防止不共享的变量被共享了,而这靠的就是Send trait

  • Send在多线程程序中起到了什么作用呢?满足Send,说明这个变量可以安全的在线程间转移。

  • 实现了Send trait表示是所有权类型

  • 不共享数据的时候,我们可以直接将变量move给生成的线程,这样,数据就被这个线程独有了。比如

    let v = vec![1, 2, 3];
    
    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });
    
    handle.join().unwrap();
    • vector v 在主线程创建以后,直接move给了生成的线程,那么除了那个线程,没有其他的地方可以使用这个vector。
    • 如果其他地方使用这个vector(比如,我们在handle.join().unwrap() )前面尝试打印vector,Rust就会报错
    • 数据要在线程之间被move需要满足Send trait。如果我们move的变量不满足Send,那么Rust将禁止程序编译通过。
      • Rc<usize>,如果move它,在多线程编程中就会报错
      • 报错的重要一句话是 Rc<i32> 不能 send 在threads safely
      • 比如上面报错的Rc。因为Rc不是多线程安全的引用计数,对应Rc并且线程安全的引用计数是Arc。
        Send:
  • 满足Send约束的类型,能在多线程之间安全的排它使用(Exclusive access is thread-safe),所有权类型

  • 满足Send约束的类型T,表示T&mut Tmut表示能修改这个引用,甚至于删除即drop这个数据)这两种类型的数据能在多个线程之间传递

    • 直白些:能在多个线程之间move值以及修改引用到的值

Sync

  • 需要又读又写共享的数据时,Rust要求共享的被读写的数据满足Sync trait
  • 如果数据不满足Sync trait,但被共享于多线程中,编译器就会报错。
    • 因为只有被保护的数据才会满足Sync,而被保护了,就说明它可以安全地在多线程间共享
      • 比如当用RefCell不能用于多线程,主要的信息是RefCell cannot be shared between threads safely
      • 因为RefCell里面的数据结构没有被保护,所以不能用于多线程中。需要使用Mutex对数据进行保护,才能将数据用于多线程中读和写。所以需要将RefCell<usize>改成Mutex<RefCell<usize>>
      • 类型的实例可以被存储在可以跨线程访问的集合中,例如sd::sync::Arcstd::sync::Mutexstd::sync::RwLock
  • Sync 和Send的关系很微妙,Sync可以理解为是Send的辅助
    • 一个类型实现了Sync trait,不一定就实现了Send trait
    • 如果一个类型实现了Send trait,那么它一定也实现了Sync trait
  • Sync:
    • 满足Sync约束的类型,能在多线程之间安全的共享使用(Shared access is thread-safe)。
    • 满足Sync约束的类型T,只表示该类型能在多个线程中读共享,即:不能move,也不能修改,仅仅只能通过引用&T来读取这个值。
  • 一个类型&T的只有在满足Send约束的条件下,类型T才能满足Sync约束 (a type T is Sync if and only if &T is Send)。即:T: Sync ≡ &T: Send
    • 这是因为将数据的引用发送到另一个线程并不会实际移动数据,而只是共享对数据的指针。
    • 因此,如果安全地将数据的引用发送到另一个线程,则该数据本身也可以同时从多个线程访问。
  • 对于那些基本的类型(primitive types)而言,比如i32类型,大多是同时满足SendSync这两个约束的,因为这些类型的共享引用(&)既能在多个多个线程中使用。

'static

  • Send和Sync,规定了多线程中,只能使用线程安全的数据
  • 使用没有GC(Garbage Collection)的编程语言时,多线程程序还要注意保证被共享的数据在被访问的时候是有效的。如果访问无效的数据,**比如Use-after-free,那么就会有Undefined Behavior。
  • 在Rust里面每一个数据都具有owner,当owner失效的时候,数据就被释放/析构。
  • 如果数据被用于多线程中,那么线程要么单独own这个数据,也就是我们前文所说的数据被move到线程中;
  • 要么数据具有'static的生命周期

什么是'static的生命周期呢?

  • 'static的生命周期表示 这个变量需要存活多久就可以存活多久。满足这个条件的有三种情况:
    • 这个数据存活得跟包含它的程序一样长,也就是数据在程序退出的时候才会被释放/析构。
      • 比如static的变量,它们在程序被load的时候存在,在程序被unload的时候释放;
      • 比如literal string,它们保存在程序的二进制代码中
    • 这个变量是owned type。
      • 什么是owned type呢?,就是这个变量是完全占有(own)这个数据,也就是所有权类型
      • 比如String, Vector,还有primitive type, usize, i64等等。
      • 所以编写多线程程序的时候,可以选择使用owned type将变量move到线程里面(这就是第一点不共享数据);
      • 当数据是share onwership的时候就使用引用计数,结合Send和Sync来进行多线程编程
    • 'static生命周期的第三种情况是如果变量包含引用,那么它只包含其他'static生命周期的引用
      • 显然,虽然这种情况,变量不拥有对应的数据,但是引用是'static,那么它也可以存活得想要多长就多长。

#rust #public

@BruceChen7 BruceChen7 added the rust label Jun 8, 2023
@BruceChen7 BruceChen7 changed the title rust中的sync和sen rust中的sync和send Jun 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant