并发的三大问题:

The Three Goblins: Reference Cells, Mutual Exclusion, and Dwarven Berserkers

The reference cell problem occurs when two threads can read and write to the same location, and the value at the location depends on the order of the reads and writes.

The second Concurrency Goblin is mutual exclusion. Imagine two threads, each trying to write a spell to a file. Without any way to claim exclusive write access to the file, the spell will end up garbled because the write instructions will be interleaved.

The third Concurrency Goblin is what I’ll call the dwarven berserker problem (aka deadlock). Imagine four berserkers sitting around a rough-hewn, circular wooden table comforting each other. “I know I’m distant toward my children, but I just don’t know how to communicate with them,” one growls. The rest sip their coffee and nod knowingly, care lines creasing their eye places. Now, as everyone knows, the dwarven berserker ritual for ending a comforting coffee klatch is to pick up their “comfort sticks” (double-bladed war axes) and scratch each other’s backs. One war axe is placed between each pair of dwarves, as shown in Figure 9-5. Their ritual proceeds thusly:

  1. Pick up the left war axe, when available.
  2. Pick up the right war axe, when available.
  3. Comfort your neighbor with vigorous swings of your “comfort sticks.”
  4. Release both war axes.
  5. Repeat.

Following this ritual, it’s entirely possible that all the dwarven berserkers will pick up their left comfort stick and then block indefinitely while waiting for the comfort stick to their right to become available, resulting in deadlock.

Clojure处理并发的工具:Future、Delay、Promise

Future创建新的线程,将其内的表达式都置于新线程中,保证当前线程不阻塞:

  1. (future (Thread/sleep 4000)
  2. (println "I'll print after 4 seconds"))
  3. (println "I'll print immediately")

Future返回一个引用值,可以用deref或者@进行解引用。解引用会引起阻塞,可以设定指定时间内未返回时的默认值。

Delay可以定义一个task,这个task不会立即执行或产生结果,它保证它内部的代码只执行一次。可以使用force让delay包围的代码强制执行(与future的deref类似)。Delay解决了互斥的问题,它能确保一次只有一个线程可以访问指定的资源。

Promises allow you to express that you expect a result without having to define the task that should produce it or when that task should run. You create promises using promise and deliver a result to them using deliver. You obtain the result by dereferencing:

  1. (def my-promise (promise))
  2. (deliver my-promise (+ 1 2))
  3. @my-promise
  4. ; => 3

Dividing tasks into a serial portion and a concurrent portion lets you safely make your code more efficient.

将任务分为并行部分和串行部分,示例:

  1. (defmacro wait [timeout & body]
  2. `(do (Thread/sleep ~timeout) ~@body)
  3. )
  4. (defmacro enqueue
  5. ([q concurrent-promise-name concurrent serialized]
  6. `(let [~concurrent-promise-name (promise)]
  7. (future (deliver ~concurrent-promise-name ~concurrent))
  8. (deref ~q)
  9. ~serialized
  10. ~concurrent-promise-name
  11. )
  12. )
  13. ([concurrent-promise-name concurrent serialized]
  14. `(enqueue (future) ~concurrent-promise-name ~concurrent ~serialized)
  15. )
  16. )
  17. (defn -main [& args]
  18. (time @(-> (enqueue saying (wait 200 "'Ello, gov'na!") (println @saying))
  19. (enqueue saying (wait 400 "Pip pip!") (println @saying))
  20. (enqueue saying (wait 100 "Cheerio") (println @saying))
  21. ))
  22. )