rust 无限的“循环”或“同时让”在性能方面是一样的?

hm2xizp9  于 2023-11-19  发布在  其他
关注(0)|答案(1)|浏览(105)

REPL(所有代码):https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=45f88d93b198a812877f9500f4daab52
在下面的代码中,我从一个通道接收,并根据一些逻辑返回Option<Message>

impl Listener {
    pub async fn receive(&mut self) -> Option<Arc<Message>> {
        if let Ok(message) = self.receiver.recv().await {
            if message.team_id == self.team_id {
                println!("this is the issue, I'm returning None and it closes the stream! block");
                return None;
            }

            Some(message)
        } else {
            None
        }
    }
}

字符串
当我在axum处理程序中收到该消息时,我使用的是一个无限的loop,如下所示:

let res = async_stream::stream! {
    loop {
        if let Some(msg) = receiver.receive().await {
            yield Ok(sse::Event::default().data(msg));
        }
    }
};


但是我担心无限的loop对于性能来说是危险的。
第一个问题
像这样使用无限循环可以吗?它总是试图获取数据吗?
我很害怕,因为我会有大量的这个函数同时存在。
第二个问题
while let版本是否与无限loop版本相同?
我试着使用下面的代码,但是如果消息是None,它会关闭流。为什么?
我应该用Result<>来代替吗?怎么用?

let s = async_stream::stream! {
    while let Some(msg) = receiver.receive().await {
            yield Ok(sse::Event::default().data(msg));
    }
};

jdzmm42g

jdzmm42g1#

while let Some(msg) = receiver.receive().await {
    yield Ok(sse::Event::default().data(msg));
}

字符串
这意味着,只要receive()的返回值是Some(...),就循环。如果是None,则退出循环。
所以如果你把它翻译成loop版本,它会是这样的:

loop {
    if let Some(msg) = receiver.receive().await {
        yield Ok(sse::Event::default().data(msg));
    } else {
        break;
    }
}


这里最大的问题是您试图与None进行什么通信。默认情况下,receive中的None s表示流结束。在这种情况下,使用while let非常有意义,因为希望在流结束后停止循环。
在你的例子中,你有这样的代码:

if message.team_id == self.team_id {
    println!("this is the issue, I'm returning None and it closes the stream! block");
    return None;
}


它返回一个None,以防消息不是你想要的。现在,它为外部创建了混合信号-在一种情况下,None用于传达流结束的信息,而在另一种情况下,它传达了消息应该被忽略的信息。
你得把这两个案子分开。我有两个办法:

  • 引入另一个Option,如Option<Option<..>>,其中外部Option传达流的结尾,内部Option传达消息应该被忽略。
  • 不要从receive函数返回,直到流结束或收到有效消息。

我个人倾向于第二种选择,我将进一步简化代码,使用.ok()?Result转换为Option并传播None

impl Listener {
    pub async fn receive(&mut self) -> Option<Arc<Message>> {
        loop {
            let message = self.receiver.recv().await.ok()?;
            if message.team_id != self.team_id {
                return Some(message);
            }
        }
    }
}


然后,使用while let版本,因为None现在再次表示您的连接已结束,在这种情况下,使用loop实际上是危险的,因为它将永远以100%的CPU功率旋转。
为了回答你对loop的担忧,loop本身并没有什么不好的,就像while一样。只有当它是一个忙碌循环时才是不好的,这意味着循环内部没有等待点。但是你的循环有一个.await,它经常挂起,所以没有CPU能量被浪费。(直到.await实际上不再等待,比如当receive()函数的连接关闭时)
补充说明:
如果你的情况有点复杂,你需要在多个地方中止当前包的处理,你可以使用loopcontinue的组合来干净地实现这一点。像这样:

impl Listener {
    pub async fn receive(&mut self) -> Option<Arc<Message>> {
        loop {
            let message = self.receiver.recv().await.ok()?;

            if message.team_id == self.team_id {
                // Skip the current message
                continue;
            }

            // Return the message if we didn't hit any `continue`s
            return Some(message);
        }
    }
}

相关问题