我发布了几百个并发http-kit.client/get
请求,这些请求通过回调将结果写入单个文件。
处理线程安全的好方法是什么?使用chan
中的<!!
和core.asyc
?
这是我要考虑的代码:
(defn launch-async [channel url]
(http/get url {:timeout 5000
:user-agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:10.0) Gecko/20100101 Firefox/10.0"}
(fn [{:keys [status headers body error]}]
(if error
(put! channel (json/generate-string {:url url :headers headers :status status}))
(put! channel (json/generate-string body))))))
(defn process-async [channel func]
(when-let [response (<!! channel)]
(func response)))
(defn http-gets-async [func urls]
(let [channel (chan)]
(doall (map #(launch-async channel %) urls))
(process-async channel func)))
感谢您的见解。
答案 0 :(得分:3)
由于您已经在示例中使用了core.async,我想我会指出一些问题以及如何解决这些问题。另一个答案提到使用更基本的方法,我完全同意一个更简单的方法就好了。但是,对于通道,您可以使用一种简单的方式来使用不涉及向量映射的数据,如果您有很多响应,这也会随着时间的推移而变大。请考虑以下问题以及我们如何解决这些问题:
(1)如果您的网址列表包含超过1024个元素,您当前的版本将会崩溃。有一个内部缓冲区用于put和take是异步的(即put!
和take!
不阻塞但总是立即返回),限制为1024.这是为了防止无限制的异步使用通道。要亲眼看看,请致电(http-gets-async println (repeat 1025 "http://blah-blah-asdf-fakedomain.com"))
。
你想要做的只是在有空间的时候把东西放在频道上。这称为背压。从go block best practices上的优秀wiki中获取页面,从http-kit回调中执行此操作的一种聪明方法是使用put!
回调选项启动下一个http get;这只会在put!
立即成功时发生,所以你永远不会有超出频道缓冲区的情况:
(defn launch-async
[channel [url & urls]]
(when url
(http/get url {:timeout 5000
:user-agent "Mozilla"}
(fn [{:keys [status headers body error]}]
(let [put-on-chan (if error
(json/generate-string {:url url :headers headers :status status})
(json/generate-string body))]
(put! channel put-on-chan (fn [_] (launch-async channel urls))))))))
(2)接下来,您似乎只处理一个响应。相反,使用循环:
(defn process-async
[channel func]
(go-loop []
(when-let [response (<! channel)]
(func response)
(recur))))
(3)这是你的http-gets-async
功能。我认为在这里添加缓冲区没什么坏处,因为它应该可以帮助你在开始时发出一连串请求:
(defn http-gets-async
[func urls]
(let [channel (chan 1000)]
(launch-async channel urls)
(process-async channel func)))
现在,您可以使用背压来处理无数个网址。要对此进行测试,请定义计数器,然后使处理函数递增此计数器以查看进度。使用易于打开的本地主机URL(不建议将数十万个请求发送到谷歌等):
(def responses (atom 0))
(http-gets-async (fn [_] (swap! responses inc))
(repeat 1000000 "http://localhost:8000"))
由于这一切都是异步的,您的函数会立即返回,您可以查看@responses
grow。
您可以做的另一个有趣的事情是,您可以选择将其作为传感器应用于频道本身,而不是在process-async
中运行处理功能。
(defn process-async
[channel]
(go-loop []
(when-let [_ (<! channel)]
(recur))))
(defn http-gets-async
[func urls]
(let [channel (chan 10000 (map func))] ;; <-- transducer on channel
(launch-async channel urls)
(process-async channel)))
有很多方法可以做到这一点,包括构建它以便通道关闭(注意上面,它保持打开状态)。如果您愿意,您可以使用java.util.concurrent
原语来帮助您,并且它们非常易于使用。可能性非常多。
答案 1 :(得分:1)
这很简单,我不会使用core.async。您可以使用原子存储使用响应的向量,然后有一个单独的线程读取原子的内容,直到它看到所有的响应。然后,在你的http-kit回调中,你可以直接swap!
对原子的响应。
如果您确实想使用core.async,我建议使用缓冲通道来防止阻止您的http-kit线程池。