Multithreading Rust中的共享互斥问题(为Arc实现异步读/异步写<;互斥<;IpStack>;)

Multithreading Rust中的共享互斥问题(为Arc实现异步读/异步写<;互斥<;IpStack>;),multithreading,rust,concurrency,mutex,Multithreading,Rust,Concurrency,Mutex,假设我有一个用户空间TCP/IP堆栈。很自然,我会将其包装在Arc中,以便与我的线程共享 我想为它实现AsyncRead和AsyncWrite,这也是很自然的,因此希望impl AsyncWrite和impl AsyncRead的库(如hyper)可以使用它 这是一个例子: use core::task::Context; use std::pin::Pin; use std::sync::Arc; use core::task::Poll; use tokio::io::{AsyncRead,

假设我有一个用户空间TCP/IP堆栈。很自然,我会将其包装在
Arc
中,以便与我的线程共享

我想为它实现
AsyncRead
AsyncWrite
,这也是很自然的,因此希望
impl AsyncWrite
impl AsyncRead
的库(如
hyper
)可以使用它

这是一个例子:

use core::task::Context;
use std::pin::Pin;
use std::sync::Arc;
use core::task::Poll;
use tokio::io::{AsyncRead, AsyncWrite};

struct IpStack{}

impl IpStack {
    pub fn send(self, data: &[u8]) {
        
    }
    
    //TODO: async or not?
    pub fn receive<F>(self, f: F) 
        where F: Fn(Option<&[u8]>){
        
    }
}

pub struct Socket {
    stack: Arc<futures::lock::Mutex<IpStack>>,
}

impl AsyncRead for Socket {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut tokio::io::ReadBuf<'_>
    ) -> Poll<std::io::Result<()>> {
        //How should I lock and call IpStack::read here?
        Poll::Ready(Ok(()))
    }
}

impl AsyncWrite for Socket {
    fn poll_write(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &[u8],
    ) -> Poll<Result<usize, std::io::Error>> {
        //How should I lock and call IpStack::send here?
        Poll::Ready(Ok(buf.len()))
    }
    //poll_flush and poll_shutdown...
}
使用核心::任务::上下文;
使用std::pin::pin;
使用std::sync::Arc;
使用core::task::Poll;
使用tokio::io::{AsyncRead,AsyncWrite};
结构IpStack{}
impl-IpStack{
发布fn发送(自身,数据:&[u8]){
}
//TODO:异步还是非异步?
发布fn接收(自我,f:f)
其中F:Fn(选项){
}
}
发布结构套接字{
堆栈:圆弧,
}
套接字的impl异步读取{
fn poll_read(
self:Pin,
cx:&mut上下文
)->投票{
//我应该如何锁定并调用IpStack::read here?
轮询::就绪(Ok(()))
}
}
套接字的impl异步写入{
fn poll_write(
self:Pin,

cx:&mut Context我发现在
tokio::sync::Mutex
上的tokio文档页面非常有用:

根据您的描述,听起来您想要:

  • 非阻塞操作
  • 一个大数据结构,管理用户空间TCP/IP堆栈管理的所有IO资源
  • 跨线程共享一个大数据结构
我建议探索一些类似于参与者的东西,并使用消息传递与生成的任务进行通信,以管理TCP/IP资源。我认为您可以将API封装为
mini redis
示例,如
tokio
的文档中引用的
AsyncRead
AsyncWrite
。这可能很简单呃,从一个API开始,返回完整结果的未来,然后进行流式处理。我认为这会更容易纠正。使用它可能会很有趣


我认为,如果您打算通过互斥体同步对TCP/IP堆栈的访问,那么您可能会得到一个
Arc
,但会得到一个像
mini redis
那样包装互斥锁的API。
tokio
文档提出的建议是,它们的互斥体实现更适合于管理IO资源与其共享原始数据,我认为这确实适合您的情况。

您不应该为此使用异步互斥锁。请使用标准

异步互斥体喜欢并允许等待锁定而不是阻塞,因此它们可以安全地在
async
上下文中使用。它们被设计为跨
await
s使用。这正是您不希望发生的!跨
await
锁定意味着互斥体可能会被锁定很长一段时间,并且将阻止希望使用
IpStack
的其他异步任务取得进展

实现/在理论上是直截了当的:要么可以立即完成,要么通过某种机制协调,在数据准备就绪并立即返回时通知上下文的唤醒器。这两种情况都不需要扩展使用底层的
IpStack
,因此使用非异步互斥锁是安全的

use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite};

struct IpStack {}

pub struct Socket {
    stack: Arc<Mutex<IpStack>>,
}

impl AsyncRead for Socket {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut tokio::io::ReadBuf<'_>,
    ) -> Poll<std::io::Result<()>> {
        let ip_stack = self.stack.lock().unwrap();

        // do your stuff

        Poll::Ready(Ok(()))
    }
}
使用std::pin::pin;
使用std::sync::{Arc,Mutex};
使用std::task::{Context,Poll};
使用tokio::io::{AsyncRead,AsyncWrite};
结构IpStack{}
发布结构套接字{
堆栈:圆弧,
}
套接字的impl异步读取{
fn poll_read(
self:Pin,
cx:&mut上下文,
)->投票{
让ip_stack=self.stack.lock().unwrap();
//做你的事
轮询::就绪(Ok(()))
}
}
除非我用
Arc
包装堆栈,否则我看不到其他更好的方法来与多个线程共享堆栈

互斥体
当然是实现类似功能最直接的方法,但我建议使用控制反转

<>在<代码>互斥< /代码>模式下,<代码> IPStase实际上是由 Socket < /Cult> s驱动的,它认为 IPStuts是共享资源。这导致了一个问题:

  • 如果一个
    Socket
    阻塞了堆栈的锁定,那么它就违反了
    AsyncRead
    的约定,花费了无限的时间来执行
  • 如果
    套接字
    在锁定堆栈时没有阻塞,而是选择使用
    try_lock()
    ,那么它可能会被饿死,因为它没有为锁“排队”。公平锁定算法,例如
    parking_lot
    提供的算法,如果你不等待,就无法让你免于饿死
相反,您可能会像系统网络堆栈那样处理问题。套接字不是参与者:网络堆栈驱动套接字,而不是相反

在实践中,这意味着
IpStack
应该有一些方法来轮询套接字,以确定下一个要向哪个套接字写入/从哪个套接字读取。用于此目的的操作系统接口,虽然不直接适用,但可能会提供一些启示。经典的是,BSD提供了;现在,像(Linux)和(FreeBSD)这样的API对于大量的连接,最好使用

一个非常简单的策略是以
select
/
poll
为松散模型,以循环方式重复扫描
Socket
连接列表,一旦可用就处理它们的挂起数据

对于基本实施,以下是一些具体步骤:

  • 当创建一个新的
    套接字
    时,在它和
    IpStack
    之间建立一个双向通道(即每个方向上一个有界通道)
  • Socket上的
    AsyncWrite
    尝试通过传出通道将数据发送到
    IpStack
    。如果通道已满,则返回
    Poll::Pending
  • Socket上的
    AsyncRead
    尝试接收数据