我正在阅读futures-preview
0.3资料,以查找如何正确“通知任何人”。在mpsc::channel
(有界)中,多个发送者可以等待接收(如果缓冲区已满)。
考虑到next_message
和unpark_one
的实现,接收者似乎每一张收据仅通知一个发送者。
我怀疑这会以select!
的形式出现,因为select!
可能会导致虚假通知。但是,我无法提出问题案例。
这是我试图混淆mpsc
:
[package]
name = "futures-mpsc-test"
version = "0.1.0"
edition = "2018"
[dependencies]
futures-preview = { version = "0.3.0-alpha.9", features = ["tokio-compat"] }
tokio = "0.1.11"
这:
#![feature(async_await, await_macro, futures_api, pin)]
use std::collections::HashSet;
use futures::prelude::*;
use futures::channel::mpsc::{channel, Sender};
use futures::channel::oneshot;
use futures::select;
async fn main2() {
let channel_len = 1;
let num_false_wait = 1000;
let num_expected_messages = 100;
let (mut send, mut recv) = channel(channel_len);
// One extra capacity per sender. Fill the extras.
await!(send.send(-2)).unwrap();
// Fill buffers
for _ in 0..channel_len {
await!(send.send(-1)).unwrap();
}
// False waits. Should resolve and produce false waiters.
for _ in 0..num_false_wait {
await!(false_wait(&send));
}
// True messages.
{
let mut send = send.clone();
await!(send.send(-2)).unwrap();
tokio::spawn(async move {
for i in 0..num_expected_messages {
await!(send.send(i)).unwrap();
}
Ok(())
}.boxed().compat());
}
// Drain receiver until all true messages are received.
let mut expects = (0..num_expected_messages).collect::<HashSet<_>>();
while !expects.is_empty() {
let i = await!(recv.next()).unwrap();
expects.remove(&i);
eprintln!("Received: {}", i);
}
}
// If `send` is full, it will produce false waits.
async fn false_wait(send: &Sender<i32>) {
let (wait_send, wait_recv) = oneshot::channel();
let mut send = send.clone();
await!(send.send(-2)).unwrap();
tokio::spawn(async move {
let mut sending = send.send(-3);
let mut fallback = future::ready(());
select! {
sending => {
sending.unwrap();
},
fallback => {
eprintln!("future::ready is selected");
},
};
wait_send.send(()).unwrap();
Ok(())
}.boxed().compat());
await!(wait_recv).unwrap();
}
fn main() {
tokio::run(async {
await!(main2());
Ok(())
}.boxed().compat());
}
我希望这会发生:
-1
填充。因此,以后的发件人被阻止。select!
的另一分支
立即完成。await!(recv.next())
时,最多只有一个 等待发送者
通知。如果通知了错误的服务员,则没有人可以将其推送到缓冲区,
即使缓冲区有一个空房间。尽管我期望如此,main2
异步功能已成功完成。为什么?
答案 0 :(得分:1)
我怀疑这会在select!的意义上起作用,因为select!可能导致错误的通知。
否,您无法使用mpsc
“混淆”一个select!
频道:
select!
不会触发任何与mspc相关的通知,它只会返回最先完成的未来。
消息队列已满时,await!(recv.next())
会通知一个生产者现在有一个进入有界通道的插槽。
换句话说:没有true waiters
和false waiters
:
当通道消息队列已满时,生产方将阻塞并等待接收方使用已排队的消息。
答案 1 :(得分:1)
进一步研究futures
源代码解决了我的问题。最后,我不能以此方式混淆mpsc。
重点在于,mpsc
的大小是灵活的,并且可以比最初指定的大小增长更多。此行为是mentioned in the docs:
该频道的容量等于
buffer + num-senders
。换句话说,每个发送方在信道容量上都有一个保证的时隙,最重要的是,所有发送方都可以使用缓冲区“先到先服务”的时隙。
是的,我在进行实验之前已经先阅读了这篇文章,但是当时我还不知道它的重要性。
考虑一个典型的有界队列实现,其中队列的大小不能超过初始指定的大小。规格是这样的:
在这种情况下,如果队列已满,则多个发件人正在等待单个资源(队列的大小)。
在多线程编程中,这是通过诸如notify_one
之类的原语来完成的。但是,在futures
中,这是容易犯错的:与多线程编程不同,通知任务不一定使用资源,因为任务可能已经放弃了获取资源(由于到select!
或Deadline
之类的结构),然后规范就被破坏了(队列未满,但所有活动的发件人都被阻止了)。
mpsc
很灵活如上所述, futures::channel::mpsc::channel
的缓冲区大小并不严格。规范总结为:
message_queue.len() == 0
时,接收方会阻止。message_queue.len() >= buffer
时,发件人可能被阻止。message_queue.len() >= buffer + num_senders
时,发件人会阻止。在这里,num_senders
基本上是Sender
的克隆数量,但在某些情况下要多一些。更准确地说,num_senders
是SenderTask
的数量。
那么,如何避免资源共享?我们还有其他状态:
SenderTask
的实例)的布尔状态为is_parked
。parked_queue
的队列,是Arc
对SenderTask
的引用的队列。通道维护以下不变式:
message_queue.len() <= buffer + num_parked_senders
。请注意,我们不知道num_parked_senders
的值。parked_queue.len() == min(0, message_queue.len() - buffer)
parked_queue
中至少有一条消息。这是通过以下算法完成的:
SenderTask
中弹出parked_queue
,如果寄件人已停放,请取消停放。is_parked
为false
。如果message_queue.len() < buffer
与parked_queue.len() == 0
一样,则所有发件人都将被停放。因此,在这种情况下,我们可以保证取得进展。is_parked
是false
,则无论如何将消息推送到队列。message_queue.len() <= buffer
,则无需进一步操作。message_queue.len() > buffer
,则将发件人置于不停车状态并推到parked_queue
。您可以轻松地检查上述算法中保持的不变性。
令人惊讶的是,发件人不再等待共享资源。而是,发送者等待其is_parked
状态。即使发送任务在完成前就被丢弃,它也将在parked_queue
中保留一段时间,不会阻塞任何内容。多么聪明!