Std::thread

导读手抄

std::thread 标准库 => 线程模型

  • Rust 线拥有自己独立的栈空间(stack)以及线程本地(local state)状态
  • 线程在操作系统的支持下能够被用户命名

Rust线程间的通信方案:

  • channel通信通道
  • 以及其他标准库std::sync中的机制
  • 或者共享内存

通常情况下使用原子计数器能够保证线程安全的互相访问。

错误退出的线程将会释放掉自己的栈空间,以及其他所有资源。

对于Rust线程,其他必须要知道内容包括:

  • thread::spawn 如何生成一个线程
  • thread::Builder()::new().name(“”) 线程的自定义命名
  • thread::current 获取当前线程
  • thread_local! 线程本地变量
  • thread stack size,by default 2MiB

手抄:标准库线程包解析

Structs

AccessError

Builder
线程工厂,配置线程变量

JoinHandle
也就是一个线程的所有方式 let joinHandle = thread::spawn(/*..*/)

LocalKey
thread local key 保存线程本地变量
每一个线程使用的时候,都会各自初始化

  • 定义用thread_local!{}
  • 使用thread_local_Variable.with(fn)

Thread
tnd啥叫,A handle to a thread!
句柄?

指针是pointer 本身这两者就有区别 handle是程序访问对象的钥匙,操作系统对对象进行管理不会改变这个句柄所指向的对象就算它实际所在的内存地址已经发生了变化,但是指针是会被修改的 如果你进行内存拷贝等操作那你手上当前握着的pointer就报废了 — 知乎评论区,handle和pointer的区别

结合上述入口,也就是线程的控制入口

Functions

  • available_concurrency
  • current Gets a handle to the thread that invokes it.获得当前线程的控制入口
  • panicking 读方法,判断线程的退出原因
  • park 阻塞直到…
  • park_timeout 阻塞直到…
  • park_timeout_ms 阻塞直到…
  • sleep 休眠
  • sleep_ms 休眠
  • spawn 生成一个线程
  • yield_now 放弃一个OS调度时间片

Type Definitions

Result 线程返回值,代表线程的退出的结果

  • Ok<_>正常退出
  • Err(e) 异常退出

std::sync::*

RustStd-sync-overview.png

同步原语

为什么CPU能够乱序执行,原因?

  1. 编译器优化,对于那些提前执行不会影响结果的指令,那就提前了。内存预加载技术!
  2. 现代单个CPU能够超标量执行,同一时刻能够执行多条命
  3. 多线程系统执行在多个不同的硬件上,提供了两种原语来处理同步问题
    1. 内存屏障,禁止指令重排
    2. 原子操作

高级的同步对象
原子操作 https://doc.rust-lang.org/nomicon/atomics.html

what is a happend-before relationship?
represented by data accesses and atomic accesses

RustStd-第 4 页.png
指令执行顺序Rust命令集:

  • Sequentially Consistent (SeqCst): 所在线程,强顺序执行
  • Release: Release之前的access都在Release之前
  • Acquire:Acquire之后的access都在Acquire之后
  • Relaxed:只能保证被标记的“读-改-写”操作的原子性

    Relaxed

    标准库文档: “In its weakest Ordering::Relaxed, only the memory directly touched by the operation is synchronized.” 只能保证被标记的“读-改-写”操作的原子性

想要理解标准库文档中的意思,就不得不下钻 std::sync::atomic::*

std::sync::atomic::*

以共享内存的方式实现线程间的通信。
正确使用原子类型的操作,原子类型能够在线程之间同步更新。
全部原子类型如下所示(Atomic Struct):

  • AtomicBool
  • AtomicI8
  • AtomicI16
  • Atomic32
  • Atomic64
  • AtomicIsize
  • AtomicPtr
  • AtomicU8
  • AtomicU16
  • AtomicU32
  • AtomicU64
  • AtomicUsize

上述原子类型,本身并不提供线程间共享的方式,但是可以把他们包进一个Arc(an atomically-reference-counted shared pointer)就可以作为共享内存在多线程间共享了。

优势:

  1. lock-free 但不完全free,其实还有点 compare-and-swap loop

注意点: 上述全部类型可能不能被全部平台使用,所以如果追求可移植性,那么就要注意上述各个类型的适用平台范围

串起Relaxed以及std::sync::atomic::*的就是原子类型的method

  1. pub struct AtomicBool { /* fields omitted */ }
  2. impl AtomicBool{
  3. pub fn load(&self, order: Ordering) -> bool
  4. pub fn swap(&self, val: bool, order: Ordering) -> bool
  5. pub fn store(&self, val: bool, order: Ordering)
  6. pub fn compare_and_swap(
  7. &self,
  8. current: bool,
  9. new: bool,
  10. order: Ordering
  11. ) -> bool
  12. pub fn compare_exchange(
  13. &self,
  14. current: bool,
  15. new: bool,
  16. success: Ordering,
  17. failure: Ordering
  18. ) -> Result<bool, bool>
  19. }

如果读-改-写操作填入Ordering的参数是Ordering::Relaxed
atomic_bool.load(Ordering::Relaxed)
那么这个操作可以被认为是原子的。

std::sync::mpsc

Multi-producer, single-consumer FIFO queue communication primitives.
多生产者,单消费者。
除了共享内存,另一个线程通信方式就是channel。
RustStd-mpsc.png

有两种通道以及两种对应的创建方式:

  • channel: (Infinite Async)无限长度的异步通道
  • sync_channel: (Bounded Sync)有限长度的,可能会阻塞于没有存储空间的通道

The following is an overview of the available synchronization objects:

Arc: Atomically Reference-Counted pointer, which can be used in multithreaded environments to prolong the lifetime of some data until all the threads have finished using it.

Barrier: Ensures multiple threads will wait for each other to reach a point in the program, before continuing execution all together.

Condvar: Condition Variable, providing the ability to block a thread while waiting for an event to occur.

mpsc: Multi-producer, single-consumer queues, used for message-based communication. Can provide a lightweight inter-thread synchronisation mechanism, at the cost of some extra memory.

Mutex: Mutual Exclusion mechanism, which ensures that at most one thread at a time is able to access some data.

Once: Used for thread-safe, one-time initialization of a global variable.

RwLock: Provides a mutual exclusion mechanism which allows multiple readers at the same time, while allowing only one writer at a time. In some cases, this can be more efficient than a mutex.