16 个版本
| 0.3.8 | 2022年6月12日 |
|---|---|
| 0.3.7 | 2021年12月19日 |
| 0.3.5 | 2021年9月4日 |
| 0.3.4 | 2021年1月31日 |
| 0.1.5 | 2017年10月27日 |
#1 in #ghost
209 每月下载量
在 10 个crate中使用 (2 直接使用)
100KB
1K SLoC
tokenlock
此crate提供了一种名为 TokenLock 的单元格类型,它只能通过展示正确的不可伪造的令牌来借用,从而解耦了权限与数据。
示例
基础
// Create a token
let mut token = IcToken::new();
// Create a keyhole by `token.id()` and use this to create a `TokenLock`.
let lock: IcTokenLock<i32> = TokenLock::new(token.id(), 1);
assert_eq!(*lock.read(&token), 1);
// Unlock the `TokenLock` using the matching token
let mut guard = lock.write(&mut token);
assert_eq!(*guard, 1);
*guard = 2;
只有匹配的 Token 的所有者才能访问其内容。 Token 不能被克隆
let lock = Arc::new(TokenLock::new(token.id(), 1));
let lock_1 = Arc::clone(&lock);
std::thread::spawn(move || {
let lock_1 = lock_1;
let mut token_1 = token;
// I have `Token` so I can get a mutable reference to the contents
lock_1.write(&mut token_1);
});
// can't access the contents; I no longer have `Token`
// lock.write(&mut token);
零大小令牌
一些令牌类型,如 BrandedToken 和 SingletonToken,完全依赖于类型安全和编译时检查来保证唯一性,不使用运行时数据来识别。因此,此类令牌的钥匙孔可以是默认构造的。 TokenLock::wrap 允许您使用默认构造的钥匙孔构建一个 TokenLock。另一方面,创建此类令牌通常有特定的要求。请参见以下使用 with_branded_token 的示例
with_branded_token(|mut token| {
// The lifetime of `token: BrandedToken<'brand>` is bound to
// this closure.
// lock: BrandedTokenLock<'brand, i32>
let lock = BrandedTokenLock::wrap(42);
lock.set(&mut token, 56);
assert_eq!(lock.get(&token), 56);
});
生命周期
返回引用的生存期受 TokenLock 和 Token 两者的限制。
let mut token = IcToken::new();
let lock = TokenLock::new(token.id(), 1);
let guard = lock.write(&mut token);
drop(lock); // compile error: `guard` cannot outlive `TokenLock`
drop(guard);
drop(token); // compile error: `guard` cannot outlive `Token`
drop(guard);
它还防止在已经存在对该值的可变引用的情况下形成对该值的引用
let write_guard = lock.write(&mut token);
let read_guard = lock.read(&token); // compile error
drop(write_guard);
同时允许多个不可变引用
let read_guard1 = lock.read(&token);
let read_guard2 = lock.read(&token);
用例:链表
操作系统内核通常需要将全局状态存储在全局变量中。链表是内核中常用的数据结构,但Rust的所有权不允许将 'static 引用到由互斥锁保护的值。常见的解决方案,如智能指针和索引引用,会对具有单线程顺序流水线和没有硬件乘数的微控制器造成沉重负担。
struct Process {
prev: Option<& /* what lifetime? */ Process>,
next: Option<& /* what lifetime? */ Process>,
state: u8,
/* ... */
}
struct SystemState {
first_process: Option<& /* what lifetime? */ Process>,
process_pool: [Process; 64],
}
static STATE: Mutex<SystemState> = todo!();
tokenlock 通过将锁粒度从受保护数据的粒度中分离出来,使得 'static 引用方法成为可能。
use tokenlock::*;
use std::cell::Cell;
struct Tag;
impl_singleton_token_factory!(Tag);
type KLock<T> = UnsyncSingletonTokenLock<T, Tag>;
type KLockToken = UnsyncSingletonToken<Tag>;
type KLockTokenId = SingletonTokenId<Tag>;
struct Process {
prev: KLock<Option<&'static Process>>,
next: KLock<Option<&'static Process>>,
state: KLock<u8>,
/* ... */
}
struct SystemState {
first_process: KLock<Option<&'static Process>>,
process_pool: [Process; 1],
}
static STATE: SystemState = SystemState {
first_process: KLock::new(KLockTokenId::new(), None),
process_pool: [
Process {
prev: KLock::new(KLockTokenId::new(), None),
next: KLock::new(KLockTokenId::new(), None),
state: KLock::new(KLockTokenId::new(), 0),
}
],
};
单元格类型
TokenLock 类型族由以下类型组成
Sync 令牌 |
!Sync 令牌² |
|
|---|---|---|
| 未固定 | TokenLock |
UnsyncTokenLock |
| 固定¹ | PinTokenLock |
UnsyncPinTokenLock |
¹也就是说,这些类型尊重 T 被设置为 !Unpin 并防止通过 &mut T 或 &Self 或 Pin<&mut Self> 暴露。
²Unsync*TokenLock 要求令牌不可 !Sync(不可在线程间共享)。作为交换,即使包含的数据不可 Sync,此类单元也可以 Sync,就像 std::sync::Mutex。
令牌类型
本包提供了以下实现 Token 的类型。
(std 仅限)IcToken 使用全局计数器(带线程局部池)来生成唯一的 128 位令牌。
(alloc 仅限)RcToken 和 ArcToken 通过引用计数的内存分配来确保它们的唯一性。
SingletonToken<Tag> 是一个单例令牌,意味着在任何程序执行过程中只能存在此类实例的一个。使用 impl_singleton_token_factory! 实例化一个 static 标志来指示 SingletonToken 的活跃状态,并允许您通过 SingletonToken::new 安全地构造它。或者,您可以使用 SingletonToken::new_unchecked,但这在使用不当的情况下是不安全的。
BrandedToken<'brand> 实现了 GhostCell 的扩展。它通过 with_branded_token 或 with_branded_token_async 创建,使得创建的令牌仅可在提供的闭包或创建的 Future 中使用。此令牌不产生运行时开销。
| 令牌 ID(钥匙孔) | 令牌(钥匙) |
|---|---|
IcTokenId |
IcToken + u128 比较 |
RcTokenId |
RcToken + usize 比较 |
ArcTokenId |
ArcToken + usize 比较 |
SingletonTokenId<Tag> |
SingletonToken<Tag> |
BrandedTokenId<'brand> |
BrandedToken<'brand> |
!Sync 令牌
UnsyncTokenLock 与 TokenLock 类似,但为非 Sync 令牌设计,对线程安全的内部类型要求较为宽松。具体来说,即使内部类型不是 Sync,它也可以是 Sync。这允许存储非 Sync 的单元格,如 Cell,并使用共享引用(所有这些都必须在同一个线程上,因为令牌是 !Sync)来读取和写入令牌。
use std::cell::Cell;
let mut token = ArcToken::new();
let lock = Arc::new(UnsyncTokenLock::new(token.id(), Cell::new(1)));
let lock_1 = Arc::clone(&lock);
std::thread::spawn(move || {
// "Lock" the token to the current thread using
// `ArcToken::borrow_as_unsync`
let token = token.borrow_as_unsync();
// Shared references can alias
let (token_1, token_2) = (&token, &token);
lock_1.read(token_1).set(2);
lock_1.read(token_2).set(4);
});
当然,!Sync 令牌不能在线程之间共享
let mut token = ArcToken::new();
let token = token.borrow_as_unsync();
let (token_1, token_2) = (&token, &token);
// compile error: `&ArcTokenUnsyncRef` is not `Send` because
// `ArcTokenUnsyncRef` is not `Sync`
std::thread::spawn(move || {
let _ = token_2;
});
let _ = token_1;
Cargo 功能
std启用依赖于std或alloc的项。alloc启用依赖于alloc的项。unstable启用不受 semver 保证的实验性项。const-default_1启用ConstDefault的实现,来自const-default ^1。
相关工作
-
ghost-cell是GhostCell的官方实现,并被正式证明是正确的。它提供了一个类似于BrandedTokenLock的接口,更简单、更专注。 -
singleton-cell中的SCell是GhostCell的更通用版本,接受任何单例令牌类型,因此它更接近我们的TokenLock。它提供了我们的BrandedToken和SingletonToken的现成等效。它牺牲了非 ZST 令牌类型以换取优势:SCell<Key, [T]>可以转换为[SCell<Key, T>]。它使用singleton-trait包(当tokenlock::SingletonToken被添加时不存在)来标记单例令牌类型。 -
qcell提供了具有不同检查机制的不同单元格类型。QCell使用 32 位整数作为令牌标识符,TCell 和TLCell使用标记类型,而LCell使用生命周期标记。 -
TokenCell来自于token-cell,与我们的SingletonToken有关,但像SCell(略有不同),它支持从&TokenCell<Token, &[T]>到&[TokenCell<Token, T>]的转换。它使用自定义特质来标记单例令牌类型。
许可协议:MIT/Apache-2.0