21 个版本
0.3.5 | 2023年3月12日 |
---|---|
0.3.4 | 2023年3月11日 |
0.2.5 | 2023年3月6日 |
0.1.8 | 2023年3月5日 |
147 在 并发 中排名
631 每月下载量
在 4 crates 中使用
115KB
2.5K SLoC
保持冷静(并调用 Clone)
多线程 Rust 程序的简单共享类型:keepcalm
允许您在并发 Rust 应用程序中简化同步代码。
名称灵感来自 @luser 的 Keep Calm and Call Clone。
概述
该库简化了多线程程序(如 Web 服务器)中使用的许多共享对象模式。
keepcalm
的优势
- 您无需事先决定同步原语。一切都是一个
Shared
或SharedMut
,无论它是互斥锁、读写锁、读写/复制/更新原语,还是只读共享std::sync::Arc
。 - 一切都可以通过
project!
来调整,这意味着您可以在任何时间调整锁的粒度,而无需重构整个系统。如果您以后需要更细粒度的锁,使用共享容器的代码不会改变! - 可写容器可以转换为只读容器,同时仍然保留其他代码更新内容的能力。
- 由于
parking_lot
crate,读写守护者是Send
。 - 每个同步原语都透明地管理中毒状态(如果在持有锁时代码
panic!
,则会中毒)。如果您不想在panic!
上中毒,构造函数可供您完全禁用此选项。 static
全局作用域的容器,用于Sync
和!Sync
对象,可以通过SharedGlobal
简单构建,并提供Shared
容器。类似地,可变的全局容器可以通过SharedGlobalMut
构建。 注意: 这需要设置--feature global_experimental
标志- 相同的原语在同步和
async
内容中都适用(需要注意的是,后者目前是实验性的):你可以使用read_async
和write_async
来等待异步锁的异步版本。 - 最小性能影响:基准测试显示,原始的
parking_lot
原语/tokio
异步容器与keepcalm
中的性能大致相同。
性能
粗略的基准测试表明,在异步和同步上下文中,与 tokio
和 parking_lot
原语的性能大致相当。虽然在某些情况下,keepcalm
的性能略快于 parking_lot
,但这很可能是测量噪声。
基准测试 | keepcalm |
tokio |
parking_lot |
---|---|---|---|
互斥锁 (异步,无竞争) | 23ns | 49ns | n/a |
互斥锁 (异步,有竞争) | 1.3ms | 1.3ms | n/a |
读写锁 (异步,无竞争) | 14ns | 46ns | n/a |
读写锁 (异步,有竞争) | (未测试) | (未测试) | (未测试) |
读写锁 (同步) | 6.8ns | n/a | (未测试) |
互斥锁 (同步) | 7.3ns | n/a | 8.5ns |
容器类型
以下容器类型可用
容器 | 等效 | 备注 |
---|---|---|
SharedMut::new |
Arc<RwLock<T>> |
这是默认的共享可变类型。 |
SharedMut::new_mutex |
Arc<Mutex<T>> |
在某些情况下,可能需要同时序列化读取和写入。例如,对于不是 Sync 的类型。 |
SharedMut::new_rcu |
Arc<RwLock<Arc<T> |
当RCU容器的写锁释放时,写入的值将被提交到容器中的值。 |
Shared::new |
Arc |
这是默认的共享不可变类型。请注意,这稍微有些冗长:Shared 不指向底层类型,并且需要调用 Shared::read 。 |
Shared::new_mutex |
Arc<Mutex<T>> |
对于不是 Sync 的类型,使用 Mutex 来序列化只读访问。 |
SharedMut::shared |
n/a | 这提供了对读写容器的只读视图,没有直接等效项。 |
以下全局容器类型可用
容器 | 等效 | 备注 |
---|---|---|
SharedGlobal::new |
staticT |
这是一个全局的 const 样式对象,用于 Send + Sync 类型。 |
SharedGlobal::new_lazy |
static Lazy<T> |
这是一个按需初始化的全局 const 风格对象,用于类型为 Send + Sync 的类型。 |
SharedGlobal::new_mutex |
static Mutex<T> |
这是一个全局 const 风格对象,用于类型为 Send 但不一定为 Sync 的类型。 |
SharedGlobalMut::new |
static RwLock<T> |
这是一个全局可变对象,用于类型为 Send + Sync 的类型。 |
SharedGlobalMut::new_lazy |
static Lazy<RwLock<T>> |
这是一个按需初始化的全局可变对象,用于类型为 Send + Sync 的类型。 |
SharedGlobalMut::new_mutex |
static Mutex<T> |
这是一个全局可变对象,用于类型为 Send 但不一定为 Sync 的类型。 |
基本语法
传统的 Rust 共享对象模式通常比较冗长且重复,例如
# use std::sync::{Arc, Mutex};
# fn use_string(s: &str) {}
struct Foo {
my_string: Arc<Mutex<String>>,
my_integer: Arc<Mutex<u16>>,
}
let foo = Foo {
my_string: Arc::new(Mutex::new("123".to_string())),
my_integer: Arc::new(Mutex::new(1)),
};
use_string(&*foo.my_string.lock().expect("Mutex was poisoned"));
如果我们想将共享字段从 std::sync::Mutex
切换到 std::sync::RwLock
,我们只需要更改四行类型,并将 lock
方法切换为 read
方法。
我们可以通过使用 keepcalm
来增加灵活性,并减少一些繁琐和冗余。
# use keepcalm::*;
# fn use_string(s: &str) {}
struct Foo {
my_string: SharedMut<String>,
my_integer: SharedMut<u16>,
}
let foo = Foo {
my_string: SharedMut::new("123".to_string()),
my_integer: SharedMut::new(1),
};
use_string(&*foo.my_string.read());
如果我们想使用 Mutex
而不是 SharedMut
在底层默认使用的 RwLock
,我们只需要将 SharedMut::new
切换为 SharedMut::new_mutex
!
SharedMut
SharedMut
对象隐藏了管理 Arc<Mutex<T>>
、Arc<RwLock<T>>
和其他同步类型背后的复杂性,通过单一接口进行管理。
# use keepcalm::*;
let object = "123".to_string();
let shared = SharedMut::new(object);
shared.read();
默认情况下,SharedMut
对象在底层使用 Arc<RwLock<T>>
,但你可以在构造时选择同步原语。SharedMut
对象 擦除 下面的原始类型,你可以相互替换它们。
# use keepcalm::*;
fn use_shared(shared: SharedMut<String>) {
shared.read();
}
let shared = SharedMut::new("123".to_string());
use_shared(shared);
let shared = SharedMut::new_mutex("123".to_string());
use_shared(shared);
管理同步原语的毒化状态也是一个挑战。如果在你持有锁时发生 panic!
,Rust 会毒化 Mutex
或 RwLock
。
SharedMut
类型允许您在构造时指定一个 PoisonPolicy
。默认情况下,如果同步原语被毒化,SharedMut
将在访问时引发 panic!
。这可以被配置为忽略毒化。
# use keepcalm::*;
let shared = SharedMut::new_with_policy("123".to_string(), PoisonPolicy::Ignore);
Shared
默认的 Shared
对象类似于 Rust 的 std::sync::Arc
,但增加了投影功能。对象也可以作为 Mutex
构建,或者是一个 SharedMut
的只读视图。
请注意,由于这种灵活性,Shared
对象比传统的 std::sync::Arc
要复杂一些,因为所有访问都必须通过 Shared::read
访问器来完成。
实验性:全局变量
注意:这需要 --feature global_experimental
标志
虽然 static
全局变量在 Rust 中可能经常是一种反模式,但此库还提供易于使用的替代方案,这些方案与 Shared
和 SharedMut
类型兼容。
可以使用 SharedGlobal
创建全局 Shared
引用。
# use keepcalm::*;
# #[cfg(feature="global_experimental")]
static GLOBAL: SharedGlobal<usize> = SharedGlobal::new(1);
# #[cfg(feature="global_experimental")]
fn use_global() {
assert_eq!(GLOBAL.read(), 1);
// ... or ...
let shared: Shared<usize> = GLOBAL.shared();
assert_eq!(shared.read(), 1);
}
同样,可以使用 SharedGlobalMut
创建全局 SharedMut
引用。
# use keepcalm::*;
# #[cfg(feature="global_experimental")]
static GLOBAL: SharedGlobalMut<usize> = SharedGlobalMut::new(1);
# #[cfg(feature="global_experimental")]
fn use_global() {
*GLOBAL.write() = 12;
assert_eq!(GLOBAL.read(), 12);
// ... or ...
let shared: SharedMut<usize> = GLOBAL.shared_mut();
*shared.write() = 12;
assert_eq!(shared.read(), 12);
}
SharedGlobal
和 SharedGlobalMut
都提供了一个 new_lazy
构造函数,允许初始化在首次访问时延迟。
# use keepcalm::*;
# use std::collections::HashMap;
# #[cfg(feature="global_experimental")]
static GLOBAL_LAZY: SharedGlobalMut<HashMap<&str, usize>> =
SharedGlobalMut::new_lazy(|| HashMap::from_iter([("a", 1), ("b", 2)]));
实验性:异步
注意:这需要 --feature async_experimental
标志
这非常实验性,可能存在安全性和/或性能问题!
Shared
和 SharedMut
类型支持一个 read_async
和 write_async
方法,该方法将使用异步运行时的 spawn_blocking
方法(或等效方法)来阻塞。使用 make_spawner
创建一个 Spawner
并将其传递给适当的锁定方法。
# use keepcalm::*;
# #[cfg(feature="global_experimental")]
static SPAWNER: Spawner = make_spawner!(tokio::task::spawn_blocking);
# #[cfg(feature="global_experimental")]
async fn get_locked_value(shared: Shared<usize>) -> usize {
*shared.read_async(&SPAWNER).await
}
# #[cfg(feature="global_experimental")]
{
let shared = Shared::new(1);
get_locked_value(shared);
}
投影
`Shared` 和 `SharedMut` 都允许对底层类型的 投影。投影可以用来选择类型的子集,或者将类型转换为特质。`project!` 和 `project_cast!` 宏可以简化此代码。
注意,投影始终与根对象相关联!如果投影被锁定,根对象也会被锁定。
转换
# use keepcalm::*;
let shared = SharedMut::new("123".to_string());
let shared_asref: SharedMut<dyn AsRef<str>> = shared.project(project_cast!(x: String => dyn AsRef<str>));
结构体/元组的子集
# use keepcalm::*;
#[derive(Default)]
struct Foo {
tuple: (String, usize)
}
let shared = SharedMut::new(Foo::default());
let shared_string: SharedMut<String> = shared.project(project!(x: Foo, x.tuple.0));
*shared_string.write() += "hello, world";
assert_eq!(shared.read().tuple.0, "hello, world");
assert_eq!(*shared_string.read(), "hello, world");
非固定大小类型
`Shared` 和 `SharedMut` 都支持非固定大小类型,但由于语言当前的限制(详见 std::ops::CoerceUnsized
),您需要以特殊方式构建它们。
支持非固定大小特质,但您需要在共享类型中指定 Send + Sync
,或者使用 project_cast!
对象
# use keepcalm::*;
// In this form, `Send + Sync` are visible in the shared type
let boxed: Box<dyn AsRef<str> + Send + Sync> = Box::new("123".to_string());
let shared: SharedMut<dyn AsRef<str> + Send + Sync> = SharedMut::from_box(boxed);
// In this form, `Send + Sync` are erased via projection
let shared = SharedMut::new("123".to_string());
let shared_asref: SharedMut<dyn AsRef<str>> = shared.project(project_cast!(x: String => dyn AsRef<str>));
使用盒子支持非固定大小切片
# use keepcalm::*;
let boxed: Box<[i32]> = Box::new([1, 2, 3]);
let shared: SharedMut<[i32]> = SharedMut::from_box(boxed);
依赖关系
~0.4–5.5MB
~16K SLoC