线程安全
线程安全指的是在有多个线程执行或访问的时候,不会有意外的行为发生。也就是说,多个线程 读取 到的数据是一致的,而多个线程写入时候不会出现数据损坏。
Rust 仅保护用户不会出现数据竞争问题,但并不能保证不会出现死锁。 死锁某种意义上属于业务逻辑层面的错误,很难被发现,可以借助 parking_lot 程序库来解决这个问题。
Rust 避免数据竞争的方法是通过组合 trait 来进行约束,spawn
的声明是这样的:
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
首先有两个泛型 F
、T
,接受一个参数 f
,返回泛型是 JoinHandle<T>
,在这之后 where
语句指定了 trait
特征。
FnOnce() -> T
:表示需要实现一个只能被调用一次的闭包。换个说法,f
是一个闭包,通过值获取所有内容并移动从环境中引用的项。Send + 'static
:闭包必须拥有Send
,因为需要按值传递给新线程。Send
表示从线程传递到线程是安全的,类似的,Sync
则表示线程之间传递引用是安全的。'static
则表示需要在整个生命周期内存活,因为新线程可能比它的调用者存活得更久,我们并不知道它什么时候返回,因此需要尽可能长久有效,直到程序结束。
Actor 模型
Actor 模型是一种概念模型,它使用名为 actors 的实体在 类型层面 实现并发。在 1973 年由 Carl Eddie Hewitt 首次提出,避免了锁和同步,并提供了一种向系统引入并发的简洁方法。
Actor 并发模式在 Erlang 之后流行起来。Erlang 是电信行业中非常流行的函数式编程语言,以其健壮性和天然的分布式特性而闻名。可以想到,Actor 模型设计本身就比较适合解决地理分布型问题,同时还具备很好的容错性。
Actor 模型由三个部分组成:
- Actor:这是 Actor 模型的核心。每个 Actor 包含地址,使用这个地址可以将消息发送到某个 actor 和
邮箱。可以理解为这是一个存储收到的消息的队列。 - FIFO:队列通常都是先进先出(First In First Out, FIFO)。actor 的地址是必须的,这样其它的 actor 才可以给它发送消息。
- Messages:Actor 之间仅通过消息进行通信,由 Actor 异步处理。
Actix
在 Cargo.toml
中引入 actix 依赖
[dependencies]
actix = "0.12"
初始化系统
use std::io;
use actix;
fn main() -> io::Result<()> {
let system = actix::System::new();
system.run()
}
Actix 使用 Tokio 作为运行时。System::new()
创建了一个新的事件循环。System::run()
启动 Tokio 的事件循环。(形象一点,相当于开了一家邮局)
在 Actix 中,actor 的创建遵循简单的步骤:
- 创建一个类型
- 定义一个消息
- actor 类型实现消息的处理程序
- 可以将它加入到某个仲裁器(arbiter)中
每个 actor 都是在仲裁器中运行。因此,创建 actor 之后,它不会立即执行,而需要等到将 actor 放入到仲裁器线程中,它们才会开始执行。
定义 actor
use std::io;
use actix::{
System, Actor, Context};
struct MyActor;
impl Actor for MyActor {
type Context = Context<