显示缓冲异步通道和通道之间区别的示例?

Examples to show the difference between Buffered Asynchronous Channels and Channels?

来自official guide

“put” operation of asynchronous channels does not block—unless the given channel was created with a buffer limit and the limit has been reached.

这是否意味着 channel-put 会被阻塞而其他线程使用 channel-get,并且 async-channel-put 仍在工作而其他线程 async-channel-get

我想知道是否有任何例子可以说明它们的区别?

你本可以发明异步通道!

您的直觉是正确的:通道往往会阻塞,但异步通道通常不会。为什么?确切的语义是什么?嗯,先来说说普通的channels.

频道

A channel 实际上是 Racket 中的原语,作为 cross-thread 通信的一种方式与线程一起实现。它们也是同步,引用the Racket reference:

Channels are synchronous; both the sender and the receiver must block until the (atomic) transaction is complete. Multiple senders and receivers can access a channel at once, but a single sender and receiver is selected for each transaction.

这意味着通道是相当原始的功能——从通道读取和写入是一个单一的操作,其中两个线程需要协调以便它们可以同时发送和接收。

打个比方,渠道代表两个人(爱丽丝和鲍勃)之间的某些物品的转移。双方就会议地点达成一致。如果爱丽丝先到,她会等鲍勃到达那里,然后把物品交给鲍勃。如果 Bob 先到,他会等待 Alice 将物品交给他。传送完成后,两人同时离开

人是线程,物品是一些Racket值,集合地点是一个频道。到达集合地点不是从通道读就是写,要等待就是线程阻塞。离开是线程恢复。

例子

考虑两个线程之间的简单交换:

#lang racket

(define channel (make-channel))

(define alice
  (thread (lambda ()
            (channel-put channel 'something)
            (displayln "Done!"))))

(define bob
  (thread (lambda ()
            (sleep 5)
            (let ([item (channel-get channel)])
              (sleep 5)
              (displayln item)))))

在上面的例子中,Done! 只会在五秒后打印,即使 Alice 线程立即将 'something 放入通道,没有等待。由于两个通道都需要协调,channel-put 等待另一个线程(在本例中为 Bob)调用 channel-get 以便交易发生。

为什么我们需要屏蔽?

此时,您可能会问自己:为什么爱丽丝需要等待?如果爱丽丝可以去会面地点,将物品丢进垃圾箱,然后立即离开,那就更好了。然后 Bob 可以在他到达时从垃圾箱中挑选物品,而 Alice 可以继续她的业务。如果垃圾箱足够大,Alice 甚至可以在 Bob 拿出任何东西之前将多个物品放入其中!

这是缓冲异步通道的想法。

缓冲异步通道

异步通道是 Racket 中一个简单的派生概念。它们可以使用内部可变缓冲区作为“bin”在通道之上实现。

异步通道由三部分组成,内部:

  1. “输入”或“入队”通道,用于将值放入缓冲区的线程。
  2. “输出”或“出队”通道,供线程从缓冲区中取出值。
  3. “缓冲区”或“队列”,一个可变值,包含所有已放入异步通道但尚未取出的值。

通道显然只是Racket通道,但是我们应该用什么来缓冲呢?嗯,Racket其实提供了an imperative queue implementation in the data/queue module, which could be used, but the Racket implementation just builds its own queue on top of mutable pairs.

要管理这三个组件之间的交互,我们只需要一个协调读写的管理器线程即可。事实证明这个实现相当简单,但我不会在这里重现。如果你愿意,可以看看 async-channel.rkt,在 Racket 中实现异步通道的模块。它有很多我没有提到的额外好处,但整个实现仍然不到 300 行。

例子

让我们回顾一下原来的例子,但是让我们使用异步通道而不是普通通道:

#lang racket

(require racket/async-channel)

(define channel (make-async-channel))

(define alice
  (thread (lambda ()
            (async-channel-put channel 'something)
            (displayln "Done!"))))

(define bob
  (thread (lambda ()
            (sleep 5)
            (let ([item (async-channel-get channel)])
              (sleep 5)
              (displayln item)))))

现在,Done! 立即打印,因为 puts 的线程不需要阻塞。它只是将它放在内部队列中,不需要关心何时获取值。

注意事项

默认情况下,将值放入异步通道永远不会阻塞(您可以设置缓冲区大小限制,但这是可选的)。但是,如果内部缓冲区为空,则从异步通道读取 绝对会阻塞。根据我的经验,这通常是您想要的行为,但如果需要,您始终可以使用 async-channel-try-get 检查值是否准备就绪。

当然,异步通道也是可变状态,所有关于突变的一般警告也适用于此。值得注意的是,单个通道不能有多个接收器,因为一旦执行了读取操作,该值就会从队列中删除。如果你想要 pub/sub 风格的事件调度,考虑使用 Multicast Asynchronous Channels 包。

不过,撇开陷阱不谈,根据我的经验,异步通道几乎总是您想要的。通道是一个重要的原语,但使用起来却很棘手。异步通道几乎可以正常工作,它们使多个线程之间的协作变得非常简单。请仔细了解它们的工作原理,以免搬起石头砸自己的脚。