【译文】Rust异步编程: Pinning
袁承兴

原文:选自《Rust异步编程》第4章 Pinning

译者注:如果你一时半会没啃动Pinning,也别心急,试试阅读这篇《Rust的Pin与Unpin - Folyd》,理解起来会容易不少。
Pinning详解
让我们尝试使用一个比较简单的示例来了解pinning。前面我们遇到的问题,最终可以归结为如何在Rust中处理自引用类型的引用的问题。

现在,我们的示例如下所示:

use std::pin::Pin;
​
#[derive(Debug)]
struct Test {a: String,b: *const String,
}
​
impl Test {fn new(txt: &str) -> Self {Test {a: String::from(txt),b: std::ptr::null(),}}
​fn init(&mut self) {let self_ref: *const String = &self.a;self.b = self_ref;}
​fn a(&self) -> &str {&self.a}
​fn b(&self) -> &String {unsafe {&*(self.b)}}
}

Test提供了获取字段a和b值引用的方法。由于b是对a的引用,因此我们将其存储为指针,因为Rust的借用规则不允许我们定义这种生命周期。现在,我们有了所谓的自引用结构。

如果我们不移动任何数据,则该示例运行良好,可以通过运行示例观察:

fn main() {let mut test1 = Test::new("test1");test1.init();let mut test2 = Test::new("test2");test2.init();
​println!("a: {}, b: {}", test1.a(), test1.b());println!("a: {}, b: {}", test2.a(), test2.b());
​
}

我们得到了我们期望的结果:

a: test1, b: test1
a: test2, b: test2

让我们看看如果将test1与test2交换导致数据移动会发生什么:

fn main() {let mut test1 = Test::new("test1");test1.init();let mut test2 = Test::new("test2");test2.init();
​println!("a: {}, b: {}", test1.a(), test1.b());std::mem::swap(&mut test1, &mut test2);println!("a: {}, b: {}", test2.a(), test2.b());
​
}

我们天真的以为应该两次获得test1的调试打印,如下所示:

a: test1, b: test1
a: test1, b: test1

但我们得到的是:

a: test1, b: test1
a: test1, b: test2

test2.b的指针仍然指向了原来的位置,也就是现在的test1的里面。该结构不再是自引用的,它拥有一个指向不同对象字段的指针。这意味着我们不能再依赖test2.b的生命周期和test2的生命周期的绑定假设了。

如果您仍然不确定,那么下面可以让您确定了吧:

fn main() {let mut test1 = Test::new("test1");test1.init();let mut test2 = Test::new("test2");test2.init();
​println!("a: {}, b: {}", test1.a(), test1.b());std::mem::swap(&mut test1, &mut test2);test1.a = "I've totally changed now!".to_string();println!("a: {}, b: {}", test2.a(), test2.b());
​
}

下图可以帮助您直观地了解正在发生的事情:

这很容易使它展现出未定义的行为并“壮观地”失败。

Pinning实践
让我们看下Pinning和Pin类型如何帮助我们解决此问题。

Pin类型封装了指针类型,它保证不会移动指针后面的值。例如,Pin<&mut T>,Pin<&T>,Pin<Box>都保证T不被移动,当且仅当T:!Unpin。

大多数类型在移动时都没有问题。这些类型实现了Unpin特型。可以将Unpin类型的指针自由的放置到Pin中或从中取出。例如,u8是Unpin,因此Pin<&mut u8>的行为就像普通的&mut u8。

但是,固定后无法移动的类型具有一个标记为!Unpin的标记。由async / await创建的Futures就是一个例子。

栈上固定
回到我们的例子。我们可以使用Pin来解决我们的问题。让我们看一下我们的示例的样子,我们需要一个pinned的指针:

use std::pin::Pin;
use std::marker::PhantomPinned;
​
#[derive(Debug)]
struct Test {a: String,b: *const String,_marker: PhantomPinned,
}
​
​
impl Test {fn new(txt: &str) -> Self {Test {a: String::from(txt),b: std::ptr::null(),_marker: PhantomPinned, // This makes our type `!Unpin`}}fn init<'a>(self: Pin<&'a mut Self>) {let self_ptr: *const String = &self.a;let this = unsafe { self.get_unchecked_mut() };this.b = self_ptr;}
​fn a<'a>(self: Pin<&'a Self>) -> &'a str {&self.get_ref().a}
​fn b<'a>(self: Pin<&'a Self>) -> &'a String {unsafe { &*(self.b) }}
}

如果我们的类型实现!Unpin,则将对象固定到栈始终是不安全的。您可以使用诸如pin_utils之类的板条箱来避免在固定到栈时编写我们自己的不安全代码。 下面,我们将对象test1和test2固定到栈上:

pub fn main() {// test1 is safe to move before we initialize itlet mut test1 = Test::new("test1");// Notice how we shadow `test1` to prevent it from being accessed againlet mut test1 = unsafe { Pin::new_unchecked(&mut test1) };Test::init(test1.as_mut());
​let mut test2 = Test::new("test2");let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };Test::init(test2.as_mut());
​println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));
}

如果现在尝试移动数据,则会出现编译错误:

pub fn main() {let mut test1 = Test::new("test1");let mut test1 = unsafe { Pin::new_unchecked(&mut test1) };Test::init(test1.as_mut());
​let mut test2 = Test::new("test2");let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };Test::init(test2.as_mut());
​println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));std::mem::swap(test1.get_mut(), test2.get_mut());println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));
}

类型系统阻止我们移动数据。

需要注意,栈固定将始终依赖于您在编写unsafe时提供的保证。虽然我们知道&'a mut T所指的对象在生命周期’a中固定,但我们不知道’a结束后数据&'a mut T指向的数据是不是没有移动。如果移动了,就违反了Pin约束。

容易犯的一个错误就是忘记隐藏原始变量,因为您可以dropPin并移动&'a mut T背后的数据,如下所示(这违反了Pin约束):

fn main() {let mut test1 = Test::new("test1");let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };Test::init(test1_pin.as_mut());drop(test1_pin);println!(r#"test1.b points to "test1": {:?}..."#, test1.b);let mut test2 = Test::new("test2");mem::swap(&mut test1, &mut test2);println!("... and now it points nowhere: {:?}", test1.b);
}

堆上固定
将!Unpin类型固定到堆将为我们的数据提供稳定的地址,所以我们知道指向的数据在固定后将无法移动。与栈固定相反,我们知道数据将在对象的生命周期内固定。

use std::pin::Pin;
use std::marker::PhantomPinned;
​
#[derive(Debug)]
struct Test {a: String,b: *const String,_marker: PhantomPinned,
}
​
impl Test {fn new(txt: &str) -> Pin<Box<Self>> {let t = Test {a: String::from(txt),b: std::ptr::null(),_marker: PhantomPinned,};let mut boxed = Box::pin(t);let self_ptr: *const String = &boxed.as_ref().a;unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr };
​boxed}
​fn a<'a>(self: Pin<&'a Self>) -> &'a str {&self.get_ref().a}
​fn b<'a>(self: Pin<&'a Self>) -> &'a String {unsafe { &*(self.b) }}
}
​
pub fn main() {let mut test1 = Test::new("test1");let mut test2 = Test::new("test2");
​println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());
}

有的函数要求与之配合使用的futures是Unpin。对于没有Unpin的Future或Stream,您首先必须使用Box::pin(用于创建Pin<Box>)或pin_utils::pin_mut!宏(用于创建Pin<&mut T>)来固定该值。 Pin<Box>和Pin<&mut Fut>都可以作为futures使用,并且都实现了Unpin。

例如:

use pin_utils::pin_mut; // `pin_utils` is a handy crate available on crates.io
​
// A function which takes a `Future` that implements `Unpin`.
fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { /* ... */ }
​
let fut = async { /* ... */ };
execute_unpin_future(fut); // Error: `fut` does not implement `Unpin` trait
​
// Pinning with `Box`:
let fut = async { /* ... */ };
let fut = Box::pin(fut);
execute_unpin_future(fut); // OK
​
// Pinning with `pin_mut!`:
let fut = async { /* ... */ };
pin_mut!(fut);
execute_unpin_future(fut); // OK

总结
如果是T:Unpin(这是默认设置),则Pin <'a, T>完全等于&'a mut T。换句话说:Unpin表示即使固定了此类型也可以移动,因此Pin将对这种类型没有影响。
如果是T:!Unpin,获得已固定T的&mut T需要unsafe。
大多数标准库类型都实现了Unpin。对于您在Rust中遇到的大多数“常规”类型也是如此。由async / await生成的Future是此规则的例外。
您可以在nightly使用功能标记添加!Unpin绑定到一个类型上,或者通过在stable将std::marker::PhantomPinned添加到您的类型上。
您可以将数据固定到栈或堆上。
将!Unpin对象固定到栈上需要unsafe。
将!Unpin对象固定到堆并不需要unsafe。使用Box::pin可以执行此操作。
对于T:!Unpin的固定数据,您必须保持其不可变,即从固定到调用drop为止,其内存都不会失效或重新利用。这是pin约束的重要组成部分。
编辑于 01-03

袁承兴:Rust异步编程 Pinning相关推荐

  1. rust异步编程--理解并发/多线程/回调/异步/future/promise/async/await/tokio

    1. 异步编程简介 通常我们将消息通信分成同步和异步两种: 同步就是消息的发送方要等待消息返回才能继续处理其它事情 异步就是消息的发送方不需要等待消息返回就可以处理其它事情 很显然异步允许我们同时做更 ...

  2. 硬核!Rust异步编程方式重大升级:新版Tokio如何提升10倍性能详解

    导读:协程或者绿色线程是近年来经常讨论的话题.Tokio作为Rust上协程调度器实现的典型代表,其设计和实现都有其特色.本文是Tokio团队在新版本调度器发布后,对其设计和实现的经验做的总结,十分值得 ...

  3. 探索 Rust 异步简化编程

    译者 | 弯月 译者 | 弯月     责编 | 欧阳姝黎 出品 | CSDN(ID:CSDNnews) Rust的异步功能很强大,但也以晦涩难懂著称.在本文中,我将总结之前提过的一些想法,并给出一些 ...

  4. Datenlord | Rust实现RDMA异步编程(二):async Rust 封装 UCX 通信库

    UCX 是一个高性能网络通信库,它作为 MPI 所依赖的通信模块之一在高性能计算领域得到广泛的使用.UCX 使用 C 语言编写,为了在 Rust 项目中使用它,我们需要将它的 C 接口包装成 Rust ...

  5. 从根本上了解异步编程体系

    作者:ronaldoliu,腾讯 IEG 后台开发工程师 或许你也听说了,摩尔定律失效了.技术的发展不会永远是指数上升,当芯片的集成度越来越高,高到 1 平方毫米能集成几亿个晶体管时,也就是人们常说的 ...

  6. 第二十一章 异步编程

    异步编程的常规方法的问题是异步程序要么做完所有的事情,要么一件事也没有做完.重写所有的代码是为了保证程序不会阻塞,否则只是在浪费时间. -------Alvaro Videla & Jason ...

  7. 深入理解Python异步编程

    声明:本文为转载内容 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知道如何使用 Tornado.Twisted. ...

  8. 深入理解 Python 异步编程

    原文地址:点击打开链接 来源:阿驹(微信公号:驹说码事) 如有好文章投稿,请点击 → 这里了解详情 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它. ...

  9. 【C++】多线程与异步编程【四】

    文章目录 [C++]多线程与异步编程[四] 0.三问 1.什么是异步编程? 1.1同步与异步 1.2 **阻塞与非阻塞** 2.如何使用异步编程 2.1 使用全局变量与条件变量传递结果 实例1: 2. ...

  10. Python网络编程(4)——异步编程select epoll

    在SocketServer模块的学习中,我们了解了多线程和多进程简单Server的实现,使用多线程.多进程技术的服务端为每一个新的client连接创建一个新的进/线程,当client数量较多时,这种技 ...

最新文章

  1. 【bzoj5082】弗拉格 矩阵乘法
  2. 【Paper】2021_Observer-Based Controllers for Incrementally Quadratic Nonlinear Systems With Disturbanc
  3. JAVA面试整理之——JAVA基础
  4. 工作三年左右的Java程序员跟大家谈谈从业心得
  5. AJAX将成为移动Web2.0时代首选开发平台
  6. C#设计模式(2)——简单工厂模式
  7. Quartz实现定时功能 job.xml文件的配置
  8. android studio 无法输入中文,Android Studio 升级到3.0后输入法中文状态下无法选词的终极解决方案...
  9. 23种设计模式[1]:单例模式
  10. 贪心算法的python实现
  11. Xamarin和Java开发安卓_将原生移动开发与Xamarin相结合
  12. Java学习日志(19-3-IO流-字节流操作)
  13. KMP模板以及入门题型总结
  14. 让Cygwin支持中文
  15. prepared statement mysql_MySQL之 Statement实现及PreparedStatement实现
  16. CrystalReports水晶报表开发中遇到的问题
  17. GhostNet论文
  18. js内置对象方法笔记
  19. python复制excel图片_python批量导出excel区域图片
  20. depts: deep expansion learning for periodic time series forecasting

热门文章

  1. MySQL基础操作命令
  2. 安卓设备安全测试框架DTF
  3. labview学习之“创建数组”函数
  4. 搜索服务Solr集群搭建 使用ZooKeeper作为代理层
  5. Docker 数据管理
  6. Linux 用户空间审计系统
  7. 禁止提示:You have new mail in /var/spool/mail/root
  8. 分布式系统常用思想和技术
  9. 五个典型的 JavaScript 面试题
  10. java.lang.NoClassDefFoundError: com.mobclick.android.MobclickAgent