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

时间:2015-12-13 04:26:36

标签: scheme racket

来自official guide

  

“put”异步通道的操作不会阻塞 - 除非创建了一个缓冲区限制并且已达到限制的给定通道。

这意味着channel-put是否会被阻止而其他线程使用channel-getasync-channel-put仍在其他线程async-channel-get上工作?

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

1 个答案:

答案 0 :(得分:11)

你本可以发明异步频道!

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

频道

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

  

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

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

使用隐喻,频道代表两个人之间的一些项目的转移,Alice和Bob。双方都同意会议地点。如果Alice先到达,她等待Bob到达那里,然后将项目交给Bob。如果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以便可以进行事务处理。

为什么我们需要阻止?

此时,您可能会问自己:为什么Alice需要等待?如果爱丽丝可以去会场,将物品放入垃圾箱,然后立即离开,那将会好得多。然后鲍勃可以在他到达时从垃圾箱中取出物品,爱丽丝可以继续她的生意。如果垃圾箱足够大,爱丽丝甚至可以在鲍勃拿出任何东西之前放入多个物品!

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

缓冲异步通道

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

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

  1. “输入”或“入队”通道,用于将值放入缓冲区的线程。
  2. “输出”或“出队”通道,用于从缓冲区中取值的线程。
  3. “缓冲区”或“队列”,一个可变值,用于保存已放入异步通道但尚未取出的所有值。
  4. 频道显然只是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!立即打印,因为put不需要阻止的线程。它只是将其粘贴在内部队列上,并且在获取值时无需关心。

    注意事项

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

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

    不过,除了陷阱之外,根据我的经验,异步频道几乎总是你想要的。频道是一个重要的原语,但它们使用起来很棘手。异步通道几乎只是工作,它们使多个线程之间的协作非常简单。只要小心了解它们是如何工作的,这样你就不会在脚下射击自己。