Clojure core.async实现睡眠理发师问题的报错与返回值求助
问题根因
- 空指针报错并非来自
customer-num的nil判断:operate-shop函数里在let块外层多套了一层括号,let块执行完关闭通道的逻辑后返回值为nil,外层括号会尝试将nil作为函数调用,直接触发IFn.invoke空指针异常,只是报错时序刚好和理发师协程的nil判断行重合导致误判。 - 无法返回理发总次数的原因:
cut-hair的返回值是core异步协程对应的channel,最终统计的理发次数是写入这个channel的值,原代码没有保存这个channel引用,也没有读取channel内的结果,自然拿不到返回值。 - 终止逻辑bug:原代码中
alts!!返回的第二个值是被选中的channel对象,不是顾客生成协程本身,手动close!这个值没有实际的终止效果,还会干扰协程正常退出。
修复后完整代码
(ns sleepbarber (:require [clojure.core.async :as a :refer [>! <! >!! <!! go go-loop chan dropping-buffer close! alts!! timeout]])) (defn cut-hair [barber-shop] (go-loop [haircuts 0] (if-some [customer-num (<! barber-shop)] (do (<! (timeout 20)) ; 单次理发耗时20ms (println (str "已完成理发数:" haircuts ",累计流失顾客:" (- customer-num haircuts))) (recur (inc haircuts))) haircuts))) ; 通道关闭无新顾客时返回总理发数 (defn operate-shop [open-time] (let [barber-shop (chan (dropping-buffer 3)) ; 等候区3个座位 barber-result-ch (cut-hair barber-shop) ; 保存理发师协程的结果通道 customer-ch (go-loop [customer 0] (<! (timeout (+ 10 (rand-int 20)))) ; 顾客到店间隔10~30ms随机 (>! barber-shop customer) (recur (inc customer)))] ; 等待开店时长结束 (alts!! [(timeout open-time)]) ; 关店:停止接收新顾客,关闭店铺通道通知理发师收尾 (close! barber-shop) (close! customer-ch) ; 等待理发师处理完排队顾客,返回最终理发总数 (<!! barber-result-ch))) ; 运行示例:开店2秒,打印最终理发数 (println "开店2秒总计完成理发数:" (operate-shop 2000))
关键修改说明
- 把
barber-shop从全局变量改为函数内部创建的局部通道,避免多次运行时的状态污染 - 移除了
operate-shop中包裹let块的多余括号,从根源消除无意义的函数调用触发的空指针 - 开店超时后先关闭店铺通道,理发师协程读取到
nil时会自动退出循环,处理完队列中剩余的等候顾客后将总理发数写入结果通道 - 显式读取理发师协程返回通道中的值,作为
operate-shop的返回值传递给调用方 - 用
if-some替代手动nil?判断,写法更符合Clojure习惯,逻辑等价不会触发空指针
内容的提问来源于stack exchange,提问作者dsagman
相关产品推荐
相关产品推荐

