Asynchronous Clojure交换!原子并行执行

Asynchronous Clojure交换!原子并行执行,asynchronous,concurrency,clojure,Asynchronous,Concurrency,Clojure,我正在使用clojure编写一个脚本,从文件中读取一系列URI作为输入,并报告它们的状态代码 我已经使用clojure.core.async/pipeline-async实现了这一点,以使用httpkit异步调用执行对URI的HTTP调用 我希望监视脚本的执行,以便为状态设置atom: let[processing atom[System/currentTimeMillis 0]] 以及跟踪进度的功能 defn跟踪进度[总进度] 交换进步 fn[[时间计数]] 让[递增计数]包含计数 现在系统/

我正在使用clojure编写一个脚本,从文件中读取一系列URI作为输入,并报告它们的状态代码

我已经使用clojure.core.async/pipeline-async实现了这一点,以使用httpkit异步调用执行对URI的HTTP调用

我希望监视脚本的执行,以便为状态设置atom:

let[processing atom[System/currentTimeMillis 0]] 以及跟踪进度的功能

defn跟踪进度[总进度] 交换进步 fn[[时间计数]] 让[递增计数]包含计数 现在系统/当前时间毫秒] if=0模增量计数最大值1整数/总计20 做 println str进度递增计数/总计|-现在时间毫秒 [现在递增计数] [时间递增计数] 在HTTP调用后使用它:

a/管道异步 相似 输出chan fn[[an id uri]结果] http/head uri{:抛出异常false :超时} fn[{:keys[状态错误]}] 跟踪总处理的进度 a/开始 如果没有?错误 做一个/>!结果[id关键字str状态] a/结束!后果 做一个/>!结果[标识:错误] a/结束!后果 输入chan 处理原子是在let表达式中使用管道异步部分创建的。 除了那根圆木,一切似乎都很好。 我发现有时候日志记录很奇怪,有这样的东西:


Progress 500/10000 | 11519ms
Progress 500/10000 | 11519msProgress 500/10000 | 11519ms

Progress 1000/10000 | 11446ms
Progress 1000/10000 | 11446ms
Progress 1500/10000 | 9503ms
Progress 2000/10000 | 7802ms
Progress 2500/10000 | 12822ms
Progress 2500/10000 | 12822msProgress 2500/10000 | 12822ms
Progress 2500/10000 | 12822ms

Progress 3000/10000 | 10623ms
Progress 3500/10000 | 9018ms
Progress 4000/10000 | 9618ms
Progress 4500/10000 | 13544ms
Progress 5000/10000 | 10541ms
Progress 5500/10000 | 10817ms
Progress 6000/10000 | 8921ms
Progress 6500/10000 | 9078ms
Progress 6500/10000 | 9078ms
Progress 7000/10000 | 9270ms
Progress 7500/10000 | 11826msProgress 7500/10000 | 11826msProgress 7500/10000 | 11826ms
输出是在shell中写入时格式化的,似乎有时同一个println被多次执行,或者fn被传递到swap!函数是并行执行的,原子中没有并发。 如果println删除str以创建要打印的字符串,则多次具有相同进度的行将完全混淆,如ProgressProgress 7500/10000 | 11826ms7500/100007500 | 11826msProgress/10000 | 11826ms

我的代码有问题吗?
或者我把atom弄错了,因为我认为它不允许并行执行改变其状态的函数?

Clojure atom是专门设计的,因此在多线程程序中,可以有多个线程执行swap!在单个原子上,如果您的程序执行此操作,那么这些更新函数f将被指定为swap!可以同时运行。交换的唯一部分!同步是一种“比较和交换”操作,它有效地执行以下操作:

锁定原子的状态 检查其当前值是否相同?在f开始执行之前包含的引用,如果是,则用f返回的新对象替换它。 解锁原子的状态。 函数f可能需要很长时间才能从当前值计算新值,但上面的临界部分只是指针比较,如果相等,则是指针赋值


这就是为什么要交换文档字符串!请注意,f可以被多次调用,因此应该没有副作用。

您想要的是序列化一组并发执行线程的输出流。您可以使用代理来序列化对一段可变状态的访问,但这里有一个没有状态的退化情况,只有副作用。在这种情况下,这就是你所需要的

例如:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

(defn do-println
  [& args]
  (apply println args))

(def lock-obj (Object.))
(defn do-println-locking
  [& args]
  (locking lock-obj
    (apply println args)))

(def sleep-millis 500)
(defn wait-and-print
  [print-fn id]
  (Thread/sleep sleep-millis)
  (print-fn (format "wait-and-print %s is complete" id)))

(defn start-threads
  [print-fn count]
  (println "-----------------------------------------------------------------------------")
  (let [futures (forv [i (range count)]
                  (future (wait-and-print print-fn i)))]
    (doseq [future futures]
      ; block until future is complete
      (deref future))))

(dotest
  (start-threads do-println 10)
  (start-threads do-println-locking 10))
典型结果:

--------------------------------------
   Clojure 1.10.2-alpha1    Java 15
--------------------------------------

Testing tst.demo.core
-----------------------------------------------------------------------------
wait-and-print 4 is completewait-and-print 3 is completewait-and-print 2 is complete
wait-and-print 8 is completewait-and-print 9 is complete
wait-and-print 6 is completewait-and-print 1 is complete

wait-and-print 7 is complete
wait-and-print 0 is complete

wait-and-print 5 is complete


-----------------------------------------------------------------------------
wait-and-print 5 is complete
wait-and-print 8 is complete
wait-and-print 7 is complete
wait-and-print 9 is complete
wait-and-print 6 is complete
wait-and-print 3 is complete
wait-and-print 0 is complete
wait-and-print 4 is complete
wait-and-print 2 is complete
wait-and-print 1 is complete
--------------------------------------
   Clojure 1.10.2-alpha1    Java 15
--------------------------------------

Testing tst.demo.core
-----------------------------------------------------------------------------
wwwwwaaawwiiiattti--taaa--nnaiddnaa--dwpp-irrptaiir-niiantnttn  -dw2ta-  ani96ipds trn- i-pcndrota-impn nrpd4itl- n eipt5tr s e7i 
 incisots   mc0cpo olmmieppstll ee
etctteo
e-
 amnidps-l pectroeai
intt- a1n di-sip rcsio nmctmpo plm3lew etaiei
spt t-lceeatone
d
m-pplreitnet
 8 is complete
-----------------------------------------------------------------------------
wait-and-print 3 is complete
wait-and-print 9 is complete
wait-and-print 8 is complete
wait-and-print 4 is complete
wait-and-print 6 is complete
wait-and-print 7 is complete
wait-and-print 0 is complete
wait-and-print 1 is complete
wait-and-print 5 is complete
wait-and-print 2 is complete
因此,您可以看到,在没有序列化的情况下,来自锁定的输出是混乱的,而第二种情况中的每个println都可以一次完成一个,即使顺序仍然是随机的

如果println一次打印一个字符而不是一次打印一个字符串,那么非同步情况下的结果将更加混乱。修改输出函数以分别打印每个字符:

(defn do-println
  [& args]
  (doseq [ch (str/join args)]
    (print ch))
  (newline))

(def lock-obj (Object.))
(defn do-println-locking
  [& args]
  (locking lock-obj
    (apply do-println args)))
典型的结果是:

--------------------------------------
   Clojure 1.10.2-alpha1    Java 15
--------------------------------------

Testing tst.demo.core
-----------------------------------------------------------------------------
wait-and-print 4 is completewait-and-print 3 is completewait-and-print 2 is complete
wait-and-print 8 is completewait-and-print 9 is complete
wait-and-print 6 is completewait-and-print 1 is complete

wait-and-print 7 is complete
wait-and-print 0 is complete

wait-and-print 5 is complete


-----------------------------------------------------------------------------
wait-and-print 5 is complete
wait-and-print 8 is complete
wait-and-print 7 is complete
wait-and-print 9 is complete
wait-and-print 6 is complete
wait-and-print 3 is complete
wait-and-print 0 is complete
wait-and-print 4 is complete
wait-and-print 2 is complete
wait-and-print 1 is complete
--------------------------------------
   Clojure 1.10.2-alpha1    Java 15
--------------------------------------

Testing tst.demo.core
-----------------------------------------------------------------------------
wwwwwaaawwiiiattti--taaa--nnaiddnaa--dwpp-irrptaiir-niiantnttn  -dw2ta-  ani96ipds trn- i-pcndrota-impn nrpd4itl- n eipt5tr s e7i 
 incisots   mc0cpo olmmieppstll ee
etctteo
e-
 amnidps-l pectroeai
intt- a1n di-sip rcsio nmctmpo plm3lew etaiei
spt t-lceeatone
d
m-pplreitnet
 8 is complete
-----------------------------------------------------------------------------
wait-and-print 3 is complete
wait-and-print 9 is complete
wait-and-print 8 is complete
wait-and-print 4 is complete
wait-and-print 6 is complete
wait-and-print 7 is complete
wait-and-print 0 is complete
wait-and-print 1 is complete
wait-and-print 5 is complete
wait-and-print 2 is complete

但是我们看到,锁定序列化了函数调用,因此活动调用必须在下一个调用开始之前完成。

如果更改函数跟踪进度,以便传递给swap的函数!只计算并返回一个新向量,然后执行if语句有条件地打印所需的内容,这样就永远不会看到具有相同增量计数值的多个日志行。你仍然可以在同一行看到多个,因为println不是原子的。明白了,所以我误解了交换!,感谢您澄清它是如何工作的!或者,如果你想线性化一切,你可以用一个代理而不是原子。