在「Rust学习笔记 - mpsc 与消息通信」中,通过 tokio::spawn 创建了一个负责发送消息的异步任务。当时主要学习任务之间的消息通信,这里继续展开任务本身的执行、等待和退出。
前言
1 | let agent_task = tokio::spawn(agent.run()); |
这里通过 tokio::spawn 创建任务,最后又通过 agent_task.await 等待它结束。这两个操作分别做了啥?
在「Rust学习笔记 - mpsc 与消息通信」中,通过 tokio::spawn 创建了一个负责发送消息的异步任务。当时主要学习任务之间的消息通信,这里继续展开任务本身的执行、等待和退出。
1 | let agent_task = tokio::spawn(agent.run()); |
这里通过 tokio::spawn 创建任务,最后又通过 agent_task.await 等待它结束。这两个操作分别做了啥?
在「Rust学习笔记 - Future、Waker 与异步任务调度」中提到过,Future 可以看作一个状态机。它会保存当前执行到了哪里、后续需要哪些局部变量,以及正在等待什么。
async/await 可以让异步代码看起来像普通的顺序代码:
1 | use tokio::time::{sleep, Duration}; |
这段代码先输出 start,等待 1 秒后再输出 finished。等待期间,run() 对应的 Future 会返回 Poll::Pending,执行到这里暂停。
定时器到期后,运行时会再次调用这个 Future 的 poll。这次不会重新输出 start,而是从 sleep(...).await 后面继续执行。
那么,Future 是怎么记住上次执行到哪里呢?
在「Rust学习笔记 - Future、Waker 与异步任务调度」中实现异步任务执行器 block_on 时,用到了 Pin 来保证某些值在内存中不被移动,想着展开学习一下。
Rust 中的值默认可以移动。移动通常发生在赋值、传参和从函数返回时,例如:
1 | let name = String::from("hallo"); |
这里不是把 String 中的文本复制了一份。name 持有的值被转移给 other,之后也不能再通过 name 访问它。对于 String 这类普通类型,移动通常不需要关心它在内存中的具体位置。只要所有权完成转移,内部管理的堆内存仍然可以正常释放和访问。
异步任务可以理解成一段能够暂停和继续执行的代码。在 Rust 中任务的暂停和恢复,主要和 Future、poll、Waker 这些概念有关。
很多语言都提供了异步编程的能力,本质上就是在“等结果”的这段时间里,不让线程白白挂住。像 C# 里有 Task 和 async/await,JavaScript 里有 Promise 和 async/await,目的都差不多,就是想让异步逻辑写起来不要那么蛋疼。
Rust 也有 async/await,但它背后的实现思路和其他语言不完全一样。比如下面这个异步函数:
1 | async fn hallo() { |
调用 hallo() 时,并不会立刻执行完整个函数,而是先返回一个 Future。
mpsc 是 multiple producer, single consumer 的缩写,表示多个发送端、一个接收端。它是一种常见的消息通信模型:多个地方可以把消息发送到同一个 channel 中,再由一个接收方按顺序取出并处理。
写服务端时,很多模块都可能需要给同一个客户端发消息。比如登录回包、消息广播、系统通知,甚至定时任务产生的推送。如果这些模块都直接操作连接对象,代码很容易碰到借用和并发访问的问题,连接本身也会变得难以维护。
一种更自然的做法是,让连接只由一个地方持有,其他模块只负责投递消息,再由持有连接的任务统一写出。mpsc 适合解决的,正是这类“多处发送、单点处理”的通信问题。
先看一个简单的例子:
1 | use std::sync::mpsc; |
最近在使用 Rust 重写一些老项目时,经常会碰到 Arc、RwLock、Mutex 以及 RefCell 这些类型。它们都和“共享”或“可变”有关,这里简单整理一下它们的区别和常见使用场景。
假设现在设计一个在线用户管理模块,并且需要支持通过编号获取对应的用户实例,看起来这个需求不复杂,但如果再往前走一步,就会发现后面很快会碰到共享所有权和内部状态修改的问题。
先看一个最简单的版本:
1 | pub struct User { |
一开始这样写没什么问题,UserMgr 负责保存用户,通过 id 取出来就行了。
Deque(Double-Ended Queue,双端队列)是一种允许在头尾两端进行插入和删除的线性数据结构。
从定义上来看,Deque 只是比 Queue 多了一端的操作能力。但与 Queue 不同的是,Deque 同时具备 Stack 和 Queue 的访问模式,因此在需要灵活处理头尾两端元素的场景中广泛应用。
它们之间的区别很直观:
Queue: FIFO(First In, First Out,先进先出)
Deque: FIFO / LIFO(先进先出 / 后进先出)
RVO(Reciprocal Velocity Obstacles,互惠速度障碍)是一种多人动态避障算法。
「数据结构与算法 - JPS 」兄弟篇
如果 A*、JPS 等寻路算法解决的是“如何到达目的地”,那么 RVO 解决的就是“在移动过程中,如何避免与动态目标发生碰撞”。常用于游戏中多单位在移动过程中实现自动避让,比如:红警、星际争霸等RTS游戏。
可以通俗的理解为:每个移动的单位都会主动互相让一点,避免路径撞车。
这类动态避障算法的核心思想是:“按照当前速度继续移动,未来会不会发生碰撞?”,也就是 Velocity Obstacle(速度障碍)。
tag:
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent:
meta: false
pages: false
posts:
title: true
date: true
path: true
text: false
raw: false
content: false
slug: false
updated: false
comments: false
link: false
permalink: false
excerpt: false
categories: false
tags: true