陈天 · Rust 编程第一课
陈天
Tubi TV 研发副总裁
23195 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 65 讲
基础篇 (21讲)
陈天 · Rust 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

33|并发处理(上):从atomics到Channel,Rust都提供了什么工具?

Mutex
Atomic
并发原语
并发模式
并发和并行
并发处理

该思维导图由 AI 生成,仅供参考

你好,我是陈天。
不知不觉我们已经并肩作战三十多讲了,希望你通过这段时间的学习,有一种“我成为更好的程序员啦!”这样的感觉。这是我想通过介绍 Rust 的思想、处理问题的思路、设计接口的理念等等传递给你的。如今,我们终于来到了备受期待的并发和异步的篇章。
很多人分不清并发和并行的概念,Rob Pike,Golang 的创始人之一,对此有很精辟很直观的解释:
Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.
并发是一种同时处理很多事情的能力,并行是一种同时执行很多事情的手段。
我们把要做的事情放在多个线程中,或者多个异步任务中处理,这是并发的能力。在多核多 CPU 的机器上同时运行这些线程或者异步任务,是并行的手段。可以说,并发是为并行赋能。当我们具备了并发的能力,并行就是水到渠成的事情。
其实之前已经涉及了很多和并发相关的内容。比如用 std::thread 来创建线程、用 std::sync 下的并发原语(Mutex)来处理并发过程中的同步问题、用 Send/Sync trait 来保证并发的安全等等。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Rust并发编程技术概览 Rust提供了丰富的工具来处理并发和异步编程,包括atomics、Mutex、Condvar、Channel和Actor model。并发是处理多个任务的能力,而并行则是同时执行多个任务的手段。在处理并发过程中,关键在于如何进行同步。本文介绍了自由竞争模式、map/reduce模式和DAG模式等几种常见的并发工作模式。这些模式可以组合处理复杂的并发场景。接下来的内容将重点讲解和深入介绍atomics、Mutex、Condvar、Channel和Actor model这五个概念。 文章首先介绍了基本的锁实现,然后指出了其存在的问题,包括多核情况下的竞争问题以及编译器和CPU的优化可能导致的乱序执行问题。为了解决这些问题,文章引入了Compare-and-swap操作,简称CAS,作为基础保证。通过使用CAS操作和Ordering参数,可以规避多核情况下的竞争问题,并处理编译器和CPU的优化相关问题。最后,文章提出了对获取锁的性能优化方法,进一步完善了锁的实现。 总的来说,本文通过介绍基本的锁实现和存在的问题,引入CAS操作和Ordering参数,以及性能优化方法,全面展示了Rust在处理并发和同步方面的丰富工具和技术特点。文章还提到了SpinLock和Mutex的区别,以及Semaphore的实现方式,为读者提供了深入了解并发编程的思路和方法。 通过本文,读者可以快速了解Rust在并发编程方面的丰富工具和技术特点,为进一步深入学习提供了基础和思路。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《陈天 · Rust 编程第一课》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(16)

  • 最新
  • 精选
  • Ethan Liu
    rust相比go在并发处理上 有什么优点和缺点?

    作者回复: 文中已经讲了,Rust 的优点是所有方案都支持,缺点是你需要考虑合适的场景用合适的工具,另外 Rust channel 在某些情况下性能不如 golang;golang 的优点是简单,CSP 一招鲜,大部分场景 channel 都能很好适用,缺点是遇到 channel 不好解决的问题,或者效率不高的问题,就比较尴尬

    2021-11-16
    7
  • D. D
    既然 Semaphore 是 Mutex 的推广,那么实现的思路应该有点类似。 参考老师文章中所说的 Mutex 的实现方法,实现 Semaphore 的一个思路是: 我们可以用一个 AtomicUsize 记录可用的 permits 的数量。在获取 permits 的时候,如果无法获取到足够的 permits,就把当前线程挂起,放入 Semaphore 的一个等待队列里。获取到 permits 的线程完成工作后退出临界区时,Semaphore 给等待队列发送信号,把队头的线程唤醒。 至于像图书馆那样的人数控制系统,tokio 的 Semaphore 文档中使用 Semaphore::acquire_owned 的例子可以说就是这种场景的模拟。

    作者回复: 👍

    2021-11-14
    2
  • Geek_b52974
    作業: use std::sync::Arc; use tokio::{sync::Semaphore, task::JoinHandle}; #[tokio::main] async fn main() { let library = Box::new(Library::new(3)); let mut tasks: Vec<JoinHandle<()>> = vec![]; for i in 0..10 { tasks.push(library.enter(move || println!("no: {}, borrow book", i))); } for task in tasks { task.await.unwrap(); } println!("{:?}", library.semaphore.available_permits()); } struct Library { semaphore: Arc<Semaphore>, } impl Library { fn new(capacity: usize) -> Self { Self { semaphore: Arc::new(Semaphore::new(capacity)), } } fn enter(&self, chore: impl Fn() + Send + 'static) -> JoinHandle<()> { let semaphore = self.semaphore.clone(); tokio::spawn(async move { // remove this you will get panic // semaphore.close(); println!("{} quota left", semaphore.available_permits()); let s = semaphore.acquire_owned().await.unwrap(); chore(); drop(s); }) } }

    作者回复: 👍

    2021-12-17
    1
  • 终生恻隐
    // [dependencies] // tokio = { version = "1", features = ["full"] } // chrono = "*" use tokio::sync::{Semaphore, TryAcquireError}; use std::sync::Arc; use std::thread; use std::time::Duration; use chrono::prelude::*; #[tokio::main] async fn main() { let semaphore = Arc::new(Semaphore::new(3)); let mut join_handles = Vec::new(); for c in 0..5 { let permit = semaphore.clone().acquire_owned().await.unwrap(); join_handles.push(tokio::spawn(async move { let local: DateTime<Local> = Local::now(); println!("count{} start time: {}", c+1, local); thread::sleep(Duration::new(10, 0)); drop(permit); })); } for handle in join_handles { handle.await.unwrap(); } }

    作者回复: Duration 有 from_secs / from_millis 等方法,不必用 Duration::new,这个接口不直观。

    2021-11-12
    1
  • Milittle
    cas是指令集指令提供的能力 一般语言封装的并发原语都是在这个基础上的

    作者回复: 对

    2022-01-13
  • 爱学习的小迪
    感觉和java对应的功能,原理一致

    作者回复: 👍

    2022-01-10
  • 核桃
    另外关于Ordering那几个的区别,个人写代码的时候,绝大部分时候,要么最简单的Relaxed,要么最严格的SeqCst,剩下的,不一定考虑那么多,除非特殊需要才去查一下区别,平时记不住的。。。

    作者回复: 不分场合地使用 Relaxed,会导致不一致的更新;不分场合地使用 SeqCst,会导致性能问题。

    2021-12-05
  • 核桃
    理解一下,对于所谓的指令原子操作,在计算机底层里面,没记错就是中断总线关闭吧。 然后那段优化的代码,就是拿不到锁先while空转一下,这里就有点像java的自旋锁的概念那样。 当然这里为什么空转性能会比spin好,从系统的角度理解,空转没有调用系统调用,那么就没有太多的进程或者线程切换,而切换这个是涉及上下文变更的。不管进程还是线程,上下文切换的时候,其实五大结构都是要考虑的,例如信号量那些是否要保存起来等等,那样代码自然会大很多了。 不知道这样理解是否对哈。

    作者回复: 不是。atomic operation 是硬件提供的无法被中断打断的指令,并不是去关闭中断。 这里我们用 atomic 实现的 mutex,就是一个 spinlock。

    2021-12-05
  • Geek_b52974
    compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) 想问一下第三个参数 为何不是 acqRel,这样其他线程会知道吗?

    作者回复: 你可以详细看文档:https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html#method.compare_exchange

    2021-11-16
  • 罗杰
    CAS 的原理挺绕的,需要好好的消化,最近也在看“Go 并发实战课”,有一些互通的地方。

    作者回复: 嗯

    2021-11-12
收起评论
显示
设置
留言
16
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部