“put”异步通道的操作不会阻塞 - 除非创建了一个缓冲区限制并且已达到限制的给定通道。
这意味着channel-put
是否会被阻止而其他线程使用channel-get
和async-channel-put
仍在其他线程async-channel-get
上工作?
我想知道是否有任何例子可以显示它们的区别?
答案 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”在通道之上实现。
异步通道由内部三部分组成:
频道显然只是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包。
不过,除了陷阱之外,根据我的经验,异步频道几乎总是你想要的。频道是一个重要的原语,但它们使用起来很棘手。异步通道几乎只是工作,它们使多个线程之间的协作非常简单。只要小心了解它们是如何工作的,这样你就不会在脚下射击自己。