【问题标题】:Examples to show the difference between Buffered Asynchronous Channels and Channels?显示缓冲异步通道和通道之间区别的示例?
【发布时间】:2016-03-18 19:25:21
【问题描述】:

来自official guide

异步通道的“put”操作不会阻塞——除非给定的通道是用缓冲区限制创建的并且已经达到限制。

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

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

【问题讨论】:

    标签: scheme racket


    【解决方案1】:

    您本可以发明异步通道!

    您的直觉是正确的:通道往往会阻塞,但异步通道通常不会。为什么?确切的语义是什么?好吧,首先,让我们谈谈普通的渠道

    频道

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

    频道是同步的;发送方和接收方都必须阻塞,直到(原子)事务完成。多个发送者和接收者可以同时访问一个通道,但每次交易选择一个发送者和接收者。

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

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

    人是线程,物品是一些球拍价值,会场是渠道。到达会面是从通道读取或写入,必须等待是线程阻塞。离开是线程恢复。

    示例

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

    #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! 只会在 5 秒后打印,即使 Alice 线程会立即将'something 放入通道中,而无需等待。由于两个通道需要协调,channel-put 等待另一个线程(在本例中为 Bob)调用channel-get 以便事务发生。

    为什么我们需要阻止?

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

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

    缓冲异步通道

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

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

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

    通道显然只是 Racket 通道,但是我们应该使用什么来缓冲呢?嗯,Racket 实际上提供了an imperative queue implementation in the data/queue module,可以使用,但是 Racket 实现只是在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 包。

    不过,除了陷阱之外,根据我的经验,异步通道几乎总是您想要的。通道是一个重要的原语,但使用起来很棘手。异步通道几乎可以正常工作,它们使多个线程之间的协作变得非常简单。请注意了解它们是如何工作的,这样您就不会在自己的脚下开枪。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-15
      • 2018-05-06
      • 2019-10-26
      • 2019-01-07
      • 2023-02-11
      • 2016-06-05
      • 2021-10-13
      • 2014-01-30
      相关资源
      最近更新 更多