从官方指南:
异步通道的"put"操作不会阻止 - 除非创建了具有缓冲区限制的给定通道并且已达到限制.
这是否意味着channel-put
在其他线程使用时会被阻止channel-get
,并且async-channel-put
在其他线程仍在工作时async-channel-get
?
我想知道是否有任何例子可以显示它们的区别?
你的直觉是正确的:渠道往往会阻塞,但异步渠道通常不会.为什么?什么是确切的语义?好吧,首先,我们来谈谈普通频道.
甲信道有效地是在球拍一个原始的,沿着螺纹作为跨线程通信的方式实现的.它们也是同步的,引用Racket参考:
频道是同步的; 发送方和接收方都必须阻塞,直到(原子)事务完成.多个发送者和接收者可以一次访问一个频道,但是为每个事务选择一个发送者和接收者.
这意味着通道是相当原始的特征 - 从通道读取和写入是单个动作,其中两个线程需要协调以便它们可以同时发送和接收.
使用隐喻,频道代表两个人之间的一些项目的转移,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实际上在data/queue
模块中提供了必要的队列实现,可以使用,但是Racket实现只是在可变对之上构建自己的队列.
为了管理这三个组件之间的交互,我们只需要一个协调读写一起的管理器线程.实现这一点非常简单,但我不会在这里重现它.如果您愿意,请查看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
s不需要阻塞的线程.它只是将其粘贴在内部队列上,并且在获取值时无需关心.
默认情况下,将值放入异步通道永远不会阻塞(您可以设置缓冲区大小限制,但这是可选的).但是,如果内部缓冲区为空,则从异步通道读取可以绝对阻止.根据我的经验,这通常是您想要的行为,但async-channel-try-get
如果您需要,您可以随时检查值是否已准备就绪.
异步通道当然也是可变状态,所有关于突变的一般警告也适用于此.值得注意的是,单个通道不能有多个接收器,因为一旦执行了读操作,就会从队列中删除该值.如果要进行发布/子样式事件调度,请考虑使用多播异步通道包.
不过,除了陷阱之外,根据我的经验,异步频道几乎总是你想要的.频道是一个重要的原语,但它们使用起来很棘手.异步通道几乎只是工作,它们使多个线程之间的协作非常简单.只是要小心了解它们是如何工作的,这样你就不会用脚射击自己.