如何在 Rust 异步应用程序正确使用锁
锁,是并发编程常用的几种技术。Rust 中也会需要用到锁,但你能正确使用吗?希望本文为你解惑。
01 引子
想象一下,你正在编写一个应用程序,也许使用tokio
。你正在添加一个又一个功能,但有一天你需要在你的任务之间使用共享的可变状态,也许是为了存储一些不断变化的共享值。
现在,你可能需要某种原语来为你提供对共享引用中某些数据的可变访问。围绕这些部分,我们喜欢称其为锁。提到锁,有些人觉得可怕,但我向你保证它并没有那么糟糕。在同步领域找出使用什么锁是很容易的。在大多数情况下,你只需从你喜欢的库中选择 Mutex
原语,std
或者 parking_lot
。
但 async
领域,选择合适的锁实现可能会令人困惑。而且 tokio
文档并没有完全做到公正,这往往只会让人们更加困惑或导致他们做出次优的决定。
但是不要害怕,通过查看几个概念并加以区分,我们可以大大简化这个问题,并为你配备所需的工具,以确定你需要什么样的锁。
02 临界区
让我们回顾一些基本术语。你以前可能遇到过临界区。它表示存在于获取锁和随后释放之间的程序部分。为了说明这一点,这里有一个写入stdout
临界区的示例代码。
let guard = state.lock().unwrap();
// 这是临界区
println!("Hello, World!");
drop(guard);
每次使用锁时,都需要仔细考虑临界区。理想情况下,我们要尽量减少这种以使其尽可能短,以及只做真正需要的。不需要在临界区内的所有内容都应始终移到临界区外。这可以让其他线程/任务更快地获取锁来提高性能,从而实现更高的吞吐量和更低的延迟。
03 数据和逻辑临界区
现在我们有了临界区,我们需要弄清楚它是什么类型。我们可以将所有临界区分为两种类型,数据临界区和逻辑临界区。
数据临界区主要用于,当你的锁包含的数据会被更新时。数据临界区应该包括读取和写入锁内的数据,但不执行任何实际计算。以下是更新当前温度的数据临界区部分的示例。
let mut weather = state.lock().unwrap();
weather.temperature = get_temperature_reading();
drop(weather);
另一种临界区称为逻辑临界区。这些不仅可以保护数据,还可以防止逻辑并发执行。当你打算存储在锁中的数据是根据锁中已有的数据计算得出时,这是最常看到和使用的。在这些情况下,你通常希望在临界区内执行计算,以防止源数据在存储计算结果之前发生更改。下面是一个典型的逻辑临界区示例,其中温度是从数据库中获取的。
let mut state = state.lock().unwrap();
state.weather = state.database.fetch_weather();
drop(state);
04 选择你的锁原语
我们已经确定了哪些类型的临界区与我们存储的数据和手头的任务相关,但这对我们有什么帮助?
线索来自对临界区的长度和可预测性以及操作系统和tokio
。
数据临界区
如果我们查看一个典型的数据临界区,它们通常很短并且执行得非常快,毕竟我们只是在复制一些数据。它们也非常可预测,我们知道复制数据总是很快,而且我们知道执行时间不依赖于某些不可预测的因素,例如外部 API 或磁盘 I/O。
基于这一点,我们知道很可能我们的临界区总是在优化使用处理器,并且它可能不会阻止其他任何人。在这些情况下,最好使用诸如来自 parking_lot
的 Mutex
之类的同步锁。这是因为它具有较低的性能开销,并且由于我们相对不受tokio
调度程序在等待时抢占我们的任务的不利影响 。
逻辑临界区
与数据临界区相比,逻辑临界区具有截然不同的特征。你通常会看到它们执行 I/O 或进行操作系统系统调用。这些操作总是有不可预测的执行时间,总体而言,锁的持有时间比数据临界区要长得多。
这意味着通常有一段时间我们的任务空闲并等待某些操作完成,例如等待数据库查询返回。
这种情况,你应该选择一个异步锁,例如由 tokio::sync
提供的 Mutex
,因为它允许 tokio 更好地调度任务以利用临界区期间的空闲等待时间。
05 总结
在被 Rust 社区的朋友多次询问后,我有了写这样一篇文章的想法。多年来,我可能已经向不同的人至少解释了十几次这个概念。
我还看到很多人在我依赖的库中为错误的任务使用错误的锁,这让我感到无济于事。
希望你已经从本文中学习了如何使用锁,但更重要的是,你现在希望拥有一个良好的框架,可以在同步和异步交互时做出明智的决策,从而解决你将来无疑将面临的类似问题。
加油!
原文链接:https://acrimon.dev/blog/async-locks/
我是 polarisxu,北大硕士毕业,曾在 360 等知名互联网公司工作,10多年技术研发与架构经验!2012 年接触 Go 语言并创建了 Go 语言中文网!著有《Go语言编程之旅》、开源图书《Go语言标准库》等。
坚持输出技术(包括 Go、Rust 等技术)、职场心得和创业感悟!欢迎关注「polarisxu」一起成长!也欢迎加我微信好友交流:gopherstudio